A GTK4 file copier and mover with filtering, integrity verification, and SSH remote transfer support.
This application was 'vibe coded' using Claude Opus 4.6.
IMPORTANT: This is an ALPHA VERSION. It has been tested (manually; through the provided test suite; and in 'production' on my own systems), but file corruption cannot be guaranteed and your data may be completely destroyed. TEST AT YOUR OWN RISK.
Binaries for Linux and MacOS are available on the releases page (expand the 'assets' section).
- Source entry field — type a local path or
host:/remote/pathdirectly into the source field - Browse Folder — opens a folder picker; the selected path fills the source field
- Browse Files — opens a file picker for individual files; the selected file path(s) fill the source field
- Browse Remote — opens an interactive SSH file browser for selecting remote source files or destination directories (see below)
- Typed paths are auto-detected:
host:/pathis treated as a remote source, plain paths as local directories or files
The Browse Remote buttons (available on both source and destination rows) open a dialog that lets you visually navigate directories on a remote SSH host:
- Host field — enter an SSH host (e.g.
user@hostnameor a~/.ssh/configalias) and click Connect - Directory listing — folders and files on the remote host are displayed in a scrollable list; double-click a folder to navigate into it
- Path bar — shows the current remote path; type a path and click Go to jump directly, or click ↑ Up to go to the parent directory
- Home directory — the browser starts at the SSH user's home directory (
~) rather than the filesystem root, so you can browse immediately without needing root-level permissions - Selection — click a file or folder to select it, then click Select to fill in the source or destination field with
host:/selected/path - For destination browsing, directories are pre-selected (since the destination is always a folder)
- Uses SSH connection multiplexing for fast, responsive navigation
- Copy — duplicate files to the destination
- Move — transfer files to the destination and remove the original
- Files Only — flatten all files into the destination directory (no subdirectories)
- Folders and Files — preserve the original directory structure at the destination
- Standard (cp/scp) — uses built-in Rust file copy for local transfers and
scpfor remote transfers (default) - rsync — uses
rsyncfor both local and remote transfers, providing:- Resumable transfers — interrupted large file copies can be picked up where they left off
- Delta transfers — when overwriting, only changed blocks are written
- Checksum verification — rsync verifies integrity during transfer with
--checksum - For remote transfers, rsync uses SSH connection multiplexing for performance
- Exclude Directories — pick directories to skip (all contents are excluded recursively)
- Exclude Files — pick individual filenames to skip wherever they appear
- + File Pattern — manually enter a wildcard pattern to exclude matching filenames (e.g.
*.jpg,test_*) - + Dir Pattern — manually enter a wildcard pattern to exclude matching directory names (e.g.
tmp*,.git*) - Clear — remove all exclusion rules
- Exclusions are displayed in a read-only scrollable list
Wildcard patterns support * (matches zero or more characters) and ? (matches exactly one character). Matching is case-insensitive and applies to the file or directory name only (not the full path). For example, te* will match a file named test.jpg regardless of where it sits in the directory tree, but will not match a file inside a directory called test/.
When a file already exists at the destination, Kosmokopy offers three strategies selected via the --conflict flag (CLI) or radio buttons (GUI):
| Destination file | Content | Skip mode | Overwrite mode | Rename mode |
|---|---|---|---|---|
| Doesn't exist | — | Copy/move normally | Copy/move normally | Copy/move normally |
| Exists, identical | Same bytes | Skip ("identical at destination") | Skip (identical) | Skip (identical) |
| Exists, different | Different bytes | Skip ("already exists") | Overwrite with source version | Keep original, save source as file_1.ext |
In Move mode, the source file is deleted after a successful transfer (or immediately if the destination is already identical). In Rename mode, the counter increments (file_1.ext, file_2.ext, …) until an unused name is found.
Local transfers:
- Every file copy is verified byte-by-byte against the source
- If verification fails on copy, the bad copy is removed
- If verification fails on move, the original is retained
- Same-filesystem moves use
rename()(instant pointer change, no data copied) - When using rsync locally, a byte-by-byte comparison is still performed after rsync's own checksum verification (defense in depth)
Remote transfers (SCP and rsync):
- After each file transfer, a SHA-256 hash of the local file is compared against a SHA-256 hash computed on the remote host via SSH
- If the hash comparison fails, the corrupt remote copy is removed and the original is retained
- Source files are never deleted during a move unless the hash verification passes
- For rsync, this SHA-256 check is performed in addition to rsync's built-in
--checksumverification
Transfer files to or from remote machines, or between two remote machines, using SSH config hosts:
Local → Remote:
- Type or browse a local source, then type
hostname:/remote/pathin the destination field (e.g.ubuntu:/home/dan/backup)
Remote → Local:
- Type
hostname:/remote/pathin the source field - Set a local destination folder
Remote → Remote:
- Type
source_host:/pathin the source field - Type
dest_host:/pathin the destination field - Files are relayed through the local machine: downloaded from source, verified, uploaded to destination, verified again
- The local machine acts as a secure intermediary — files are staged in a temporary directory that is cleaned up after transfer
Common remote features:
- Hostnames must match entries in
~/.ssh/config - Uses SSH connection multiplexing for performance
- Creates remote directories automatically
- Remote conflict detection checks existing files before transfer (skip, overwrite, or rename)
- Post-transfer SHA-256 hash verification ensures data integrity
- Source files are deleted only after hash verification passes (move mode)
- Both Standard (scp) and rsync methods are supported for all remote transfer directions
- Real-time progress bar showing file count and current filename
- Cancel button — gracefully stop a running transfer at the next file boundary; already-copied files are kept, the remaining files are skipped, and a summary is shown
- In CLI mode, press Ctrl+C to cancel; the JSON output reports
"status":"cancelled"with counts of files transferred before stopping - Completion dialog with summary of copied, skipped, and excluded files
- Detailed skip reasons (identical, already exists, different version)
- Scrollable error list if any transfers fail
- Rust toolchain (edition 2021, Cargo 1.70+)
- GTK4 development libraries (>= 4.10)
pkg-config
If you don't already have Rust and Cargo installed:
# Using wget (recommended on Ubuntu where curl may be a snap with sandbox restrictions)
wget -qO- https://sh.rustup.rs | sh
# Or using curl
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shAccept the defaults, then restart your shell or run:
source "$HOME/.cargo/env"brew install gtk4GTK4 >= 4.10 is required. Ubuntu 24.04 LTS ships GTK4 4.14 and is the recommended build platform. Ubuntu 22.04 ships GTK4 4.6 which is too old.
sudo apt install libgtk-4-dev build-essential pkg-config- GTK4 runtime libraries
sshandscp(only for remote transfers via Standard method — present on any system with SSH configured)rsync(only when rsync transfer method is selected — commonly pre-installed on macOS and Linux)sha256sumorshasumon the remote host (for remote transfer hash verification — present on virtually all Unix systems)
cargo build --releaseThe binary is at target/release/kosmokopy.
./macos/build-dmg.shCreates target/macos/Kosmokopy-0.1.0-arm64.dmg containing a drag-to-install .app bundle.
Note: GTK4 must be installed via Homebrew on the target Mac.
./appimage/build-appimage.shCreates a portable target/appimage/Kosmokopy-0.1.0-x86_64.AppImage.
Note: GTK4 runtime libraries must be installed on the target system.
- Select source — choose one of:
- Type a local path directly in the source field
- Click "Browse Folder" to select a local directory (fills the source field)
- Click "Browse Files" to pick individual local files
- Click "Browse Remote" to visually browse and select files or folders on a remote SSH host
- Type
host:/remote/pathin the source field for a remote source
- Set destination — browse for a local folder, type a local path, enter
host:/pathfor a remote destination, or click "Browse Remote" to pick a remote directory interactively - Choose mode — Copy or Move, Files Only or Folders and Files
- Choose transfer method — Standard (cp/scp) or rsync
- Set exclusions (optional) — use the picker buttons or type wildcard patterns (e.g.
*.log,tmp*) and click "+ File Pattern" or "+ Dir Pattern" - Choose conflict handling (optional) — select Skip (default), Overwrite, or Rename to control how filename collisions are resolved
- Click Transfer
| Source | Destination | How it works |
|---|---|---|
| Local folder/files | Local path | Direct file copy/move with byte-by-byte verification |
| Local folder/files | host:/path |
Upload via SCP or rsync with SHA-256 verification |
host:/path |
Local path | Download via SCP or rsync with SHA-256 verification |
host1:/path |
host2:/path |
Download to local temp → verify → upload to dest → verify → clean up |
Kosmokopy includes an external Python test suite that exercises the real Rust binary via its --cli headless mode, then verifies results in Python.
| Test file | What it covers |
|---|---|
test_local.py |
Local copy and move (standard + rsync), directory structure preservation, strip-spaces, destination auto-creation, single-file copy/move |
test_conflicts.py |
All three conflict modes — Skip, Overwrite, Rename — for both local and remote destinations, including the _1, _2, … auto-rename numbering scheme |
test_exclusions.py |
Exact directory and file exclusions, wildcard directory and file exclusions (*, ?), combined exclusion rules, case-insensitive matching |
test_integrity.py |
Byte-by-byte identity after copy, SHA-256 hash verification, empty & large binary files, move-mode source deletion, rsync integrity,plus 30 negative/corruption tests — single-byte flip, appended byte, truncation, content replacement, file deletion, empty↔nonempty swap, nested corruption, remote corruption (append/truncate/replace/delete), and hash-helper self-tests |
test_remote.py |
Local→remote (SCP + rsync), remote→local (SCP + rsync), remote→remote relay (SCP + rsync), move-mode source deletion, conflict handling on remote, exclusions, strip-spaces, single-file remote upload/download, real source directory upload |
test_cancel.py |
Graceful SIGINT cancellation — partial copy count, copied files intact, no errors, move-cancel preserves un-transferred sources, rsync cancel, cancel with exclusions, immediate cancel |
Every test invokes kosmokopy --cli (the headless mode) via Python's subprocess module. The Rust binary performs the actual file operation and prints a JSON result. Python then inspects the destination filesystem (or remote host via SSH) to verify correctness — file existence, SHA-256 hash match, source deletion (for moves), and correct error handling.
Remote tests use real SSH connections and are automatically skipped when the remote host environment variables are not set.
The integrity test file includes 30 negative tests that prove the verification checks genuinely catch data corruption. The methodology is:
- Copy files using
kosmokopy --cli(the real Rust binary performs the transfer) - Verify the copy is intact (SHA-256 hash match, byte-identical comparison)
- Deliberately corrupt the destination file using one of the techniques below
- Assert that
sha256_of_file()andfiles_are_identical()now detect the mismatch
| Technique | How it works | What it proves |
|---|---|---|
| Single-byte flip | XOR one byte at the midpoint with 0xFF |
Detects minimal single-bit corruption |
| Appended byte | Write \x00 at end of file |
Detects extra data appended after transfer |
| Truncation | Rewrite file with only the first half of its content | Detects incomplete or truncated transfers |
| Content replacement | Replace entire file contents with different text of same length | Detects wholesale content substitution |
| File deletion | Remove the destination file entirely | Detects missing files |
| Empty → non-empty | Write data into a previously zero-byte file | Detects corruption of empty files |
| Non-empty → empty | Truncate a file to zero bytes | Detects total data loss |
| Nested corruption | Corrupt files 1 and 2 levels deep in subdirectories | Proves checks work at any directory depth |
| Pinpointed corruption | Corrupt one file among many, verify the rest are still intact | Proves corruption detection is per-file, not global |
Each corruption technique is tested after transfers using:
- Standard local copy (9 tests) — single byte flip, append, truncate, replace, delete, empty↔nonempty swap, nested, deeply nested
- rsync local copy (3 tests) — byte flip, truncation, content replacement
- Move mode (2 tests) — corrupt after move, pinpoint one corrupted file among many
- Flat / files-only mode (1 test) — corruption in flattened output
- Strip-spaces mode (1 test) — corruption after space-stripping renames
- Remote upload (5 tests) — append, truncate, replace, and delete the remote file via SSH; pinpoint one corrupted remote file
- Remote download (3 tests) — corrupt, truncate, and delete the local copy; verify against still-intact remote
The sha256_of_file() and files_are_identical() helpers are themselves validated:
- Identical files → match
- Different files → mismatch
- Same size, different content → mismatch
- One-byte difference → mismatch
- Empty vs non-empty → mismatch
- Both empty → match
The --cli flag runs the application headlessly (no GTK window). It accepts the same options as the GUI but as command-line arguments:
kosmokopy --cli --src <dir> --dst <dir> [options]
| Flag | Description |
|---|---|
--src <path> |
Source directory |
--dst <path> |
Destination directory (local or host:/path) |
--src-files <a,b,c> |
Comma-separated list of individual source files |
--move |
Move instead of copy |
--conflict <skip|overwrite|rename> |
Conflict resolution strategy (default:skip) |
--strip-spaces |
Remove spaces from destination filenames and directory names |
--mode <files|folders> |
Transfer mode (default:folders) |
--method <standard|rsync> |
Transfer method (default:standard) |
--exclude <pattern> |
Exclusion pattern (repeatable) |
Output is a single JSON line:
{"status":"finished","copied":3,"skipped":[],"excluded_files":0,"excluded_dirs":0,"errors":[]}If cancelled via Ctrl+C, the status is "cancelled" and counts reflect work done before stopping.
Prerequisites: Python 3.9+, pipenv, pytest
# Install test dependencies (one-time)
pipenv install --dev pytest
# Run all local tests
pipenv run python -m pytest tests/ -v
# Run a specific test file
pipenv run python -m pytest tests/test_integrity.py -v
# Run a specific test class
pipenv run python -m pytest tests/test_exclusions.py::TestWildcardMatching -vRemote tests require SSH access to one or two hosts listed in ~/.ssh/config. Set the following environment variables before running:
# Single remote host (local↔remote tests)
export KOSMOKOPY_TEST_REMOTE_HOST="myserver" # or "user@myserver"
export KOSMOKOPY_TEST_REMOTE_PATH="/tmp/kosmokopy_test"
# Second remote host (remote→remote relay tests)
export KOSMOKOPY_TEST_REMOTE_HOST2="otherserver"
export KOSMOKOPY_TEST_REMOTE_PATH2="/tmp/kosmokopy_test2"
# Optional: real source directory (uploads first 10 files and verifies hashes)
export KOSMOKOPY_TEST_SOURCE_DIR="/path/to/real/files"
pipenv run python -m pytest tests/ -vRemote test directories are created automatically and cleaned up after each test.
A timestamped report file is automatically generated after every test run and saved to tests/reports/. Each report includes:
- Date, duration, platform, Python version, binary path
- Remote host configuration
- Full pass/fail/skip summary with counts
- Failure details (traceback) for any failed tests
- Complete list of all passed and skipped tests
Report filenames follow the pattern report_YYYY-MM-DD_HHMMSS.txt.
Dan Bright — dan@danbright.uk
This code was primarily authored using artificial intelligence (Claude Opus 4.6 model).
Copyright (C) 2026 Dan Bright
This project is licensed under the GNU General Public License v3.0 — see LICENSE for details.
All third-party dependency licenses (MIT, Apache-2.0, Unlicense) are bundled in THIRD-PARTY-LICENSES.txt.
- Added remote file browser — "Browse Remote" buttons on both source and destination rows open an interactive SSH directory browser; navigate folders on any SSH host, select files or directories, and the chosen
host:/pathfills the entry field automatically; starts at the user's home directory for immediate usability - Added Cancel button — a "Cancel" button appears during transfers in the GUI; clicking it gracefully stops the transfer at the next file boundary, keeps already-copied files, and shows a summary dialog; in CLI mode, pressing Ctrl+C sends a cancellation signal with the same graceful behaviour
- Fixed remote single-file copy — copying a single file from a remote source now works correctly (previously returned 0 files because
collect_remote_filesskipped the sole entry) - Unified source entry field — replaced the separate read-only source label and "Remote source" text field with a single editable entry; type a local path,
host:/path, or use the Browse buttons to fill it; the source type (local directory, local file, or remote) is auto-detected - Added cancel test suite (
test_cancel.py) — 10 tests covering graceful SIGINT cancellation: partial copy counts, file integrity after cancel, move-cancel source preservation, rsync cancel, cancel with exclusions, and immediate cancel - Added single-file tests — 4 local single-file tests (standard/rsync/move/directory-with-one-file) and 3 remote single-file tests (download SCP, download rsync, upload)
- Added
--cliheadless mode — run all transfer operations from the command line without opening a GTK window; accepts--src,--dst,--move,--conflict,--strip-spaces,--mode,--method,--excludeand prints a JSON result - Rewrote test suite to use the real binary — all tests now invoke
kosmokopy --clivia subprocess; Python only verifies results (file existence, SHA-256 hashes, source deletion) - Added 30 negative/corruption tests — deliberately tamper with copied files (single-byte flip, append, truncate, replace, delete, empty↔nonempty) and verify that SHA-256 and byte-comparison checks catch every corruption; covers local standard, rsync, move, flat mode, strip-spaces, remote upload, and remote download scenarios; includes hash-helper self-tests
- Automatic test report generation — a timestamped report is saved to
tests/reports/after every pytest run, including platform info, remote host config, duration, and full pass/fail/skip breakdown with failure details
- Added conflict mode selection — new
ConflictModeenum (Skip / Overwrite / Rename) replaces the boolean overwrite toggle; radio buttons in the UI let users choose how filename collisions are handled - Auto-rename on conflict — when Rename mode is selected, conflicting files are saved as
file_1.ext,file_2.ext, etc.; works for both local and remote destinations - Added external Python test suite — tests across 6 files (
test_local,test_conflicts,test_exclusions,test_integrity,test_remote,test_cancel) covering local/remote copy and move, all conflict modes, wildcard exclusions, SHA-256 verification, corruption detection, cancellation, and remote-to-remote relay; remote tests auto-skip when environment variables are unset - Added remote source support — new "Remote source" text entry field accepts
host:/pathto pull files from a remote machine; overrides local source selection when filled in - Added remote-to-local transfers — download files from a remote host to a local destination with SHA-256 hash verification; supports both SCP and rsync methods
- Added remote-to-remote transfers — transfer files between two remote hosts using the local machine as a secure relay; files are downloaded, verified, uploaded, and verified again before source deletion (move mode)
- Added wildcard exclusion patterns — new "+ File Pattern" and "+ Dir Pattern" buttons allow manual entry of
*and?wildcard patterns for flexible exclusion matching - Added rsync transfer method — available for both local and remote transfers via a new "Transfer method" radio button group (Standard / rsync)
- Added SHA-256 hash verification for remote transfers — both SCP and rsync remote transfers now verify integrity by comparing local and remote SHA-256 hashes after each file transfer
- Hardened SCP remote move safety — source files are no longer deleted after SCP transfer without cryptographic hash verification; previously relied solely on SCP exit status
- Local rsync worker — uses
rsync -a --checksumwith a follow-up byte-by-byte comparison for defense in depth; same-filesystem moves still use atomicrename() - Remote rsync worker — uses
rsync -az --checksumover SSH with connection multiplexing, plus post-transfer SHA-256 verification before any source deletion - Added
sha2crate dependency for local SHA-256 hash computation - Updated runtime requirements — documented
rsyncandsha256sum/shasumas optional runtime dependencies
