No description
  • Rust 71.2%
  • TypeScript 17.4%
  • Svelte 10.6%
  • JavaScript 0.3%
  • CSS 0.3%
  • Other 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Fraxured 81774da961
Some checks failed
CI / check (push) Has been cancelled
Merge phase 6: partner share
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 18:32:12 -05:00
.cargo chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
.forgejo/workflows feat: partner share code service 2026-10-17 14:55:51 -05:00
.github/workflows test: FTP and tabs e2e, CI and README for phase 2 2026-10-17 06:44:07 -05:00
.vscode chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
crates/share-wire feat: partner share code service 2026-10-17 14:55:51 -05:00
docker docs: self-hosting partner share servers 2026-10-17 16:32:00 -05:00
docs docs: FileZilla Pro comparison and phase 6-7 design 2026-10-17 15:15:33 -05:00
e2e test: partner share e2e, README for phase 6 2026-10-17 16:32:07 -05:00
scripts fix: final-review fixes for stale release installers, WinSCP UTF-8 names and keychain write failures 2026-10-17 13:07:17 -05:00
server fix: code service penalises squatters; quitting Hatch ends a share session 2026-10-17 17:56:27 -05:00
src fix: partner share surfaces new offers and shows progress in the dialog 2026-10-17 17:53:49 -05:00
src-tauri fix: code service penalises squatters; quitting Hatch ends a share session 2026-10-17 17:56:27 -05:00
static chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
.gitattributes test: queue and remote edit e2e, README for phase 3 2026-10-17 09:13:47 -05:00
.gitignore chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
package-lock.json feat: dual file panes with keyboard, context menu and drag 2026-10-16 19:10:11 -05:00
package.json feat: transfer panel, e2e smoke test, CI 2026-10-16 19:33:12 -05:00
playwright.config.ts feat: transfer panel, e2e smoke test, CI 2026-10-16 19:33:12 -05:00
README.md docs: phase 6 installer size 2026-10-17 17:08:52 -05:00
svelte.config.js chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
tsconfig.json chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
vite.config.js chore: scaffold Tauri + Svelte app with error model 2026-10-16 17:41:15 -05:00
vitest.config.ts ci: sign release builds and publish latest.json for the updater 2026-10-17 12:53:08 -05:00

Hatch

A lightweight, secure, modern desktop client for SFTP, FTPS and FTP, in the spirit of WinSCP and FileZilla. Built with Tauri 2 (Rust) and Svelte 5.

See the design spec for the full design.

What works today

Phase 6: Partner Share

  • Send files straight to another Hatch user. You don't need port forwarding, an account, or a cloud drive. Click Partner Share in the top bar.
    1. Pick what to send. Files selected in your local pane are already on the list. Add more with Add files… / Add folder…, or drag them in from Explorer or Finder. Then click Get a code.
    2. Pass on the code, for example 7-clockwork-crossover, by chat, voice or anything else. Copy link gives a hatch://share/… link instead. Codes work once and expire after 10 minutes.
    3. Your partner enters the code on the Receive tab, or clicks the link. A link only fills in the code: nothing connects until they click Connect.
    4. They see what's coming and choose Accept or Decline. Files land in Downloads\Hatch unless they pick another folder, and Hatch remembers their choice.
    • Transfers show in the transfer panel as To … / From …, with the usual pause, resume, cancel and retry. Either of you can send more, or Send files back…, until one of you clicks End session.
  • Encrypted end to end. The code drives a password-based key exchange (SPAKE2). A wrong guess burns the code, and nobody can step in between without it. The badge says whether you're connected direct or via relay. Either way the servers only ever pass along encrypted bytes, and they never see the words of the code.
  • Receiving safely.
    • Nothing arrives until the receiver clicks Accept. Programs and scripts are tagged Program.
    • Received files are marked as downloaded from the internet (Windows and macOS), so the OS warns before running them. Hatch never opens them itself.
    • An offer with a file name that could land outside the save folder is refused whole.
  • Dropped connections (Wi-Fi changes, a laptop going to sleep) get 5 minutes to come back. Files carry on from where they stopped.
  • Self-hosted servers: a small code service and an iroh relay. See docker/partner-share/README.md. Settings → Partner Share sets your name and can point Hatch at other servers. Both partners need the same servers.

Phase 5: import and updates

  • Import sites from FileZilla and WinSCP. Open Sites → Import…. Hatch finds FileZilla's sitemanager.xml and WinSCP's sessions (registry or WinSCP.ini) on its own, or you can choose a file.
    • You see every site first, with its folder, and untick anything you don't want. Sites you already have (same host, user and port) are marked Already in your sites and start unticked, so importing again doesn't make copies.
    • Passwords go straight to your OS keychain and never reach the app window. Passwords protected by a FileZilla or WinSCP master password aren't imported; Hatch asks for them when you connect.
    • FileZilla's FTP (TLS if available) becomes FTPS here, because Hatch never falls back to plain FTP. Edit the site and choose FTP if a server doesn't support TLS. WinSCP's SCP sites connect with SFTP. S3, WebDAV and other protocols Hatch doesn't speak are listed but not imported.
  • Updates. Hatch checks for a new version when it starts (turn this off in Settings → About) and offers Install and restart. If transfers or remote edits are running, it asks first.
    • Updates are signed, and Hatch installs only an update whose signature matches the key built into it. The check runs in the app's backend; the window itself has no network access.
    • Settings → About shows the version and has Check for updates.

Phase 4: folder sync

  • Compare and sync folders with the sync button in the connection bar. Pick a folder on this computer, a folder on the server and a direction: Upload to server, Download to this computer or Both ways.
  • Preview first. Hatch lists every difference as a tree (new, changed, newer on either side, and anything it can't sync) and changes nothing until you confirm. Untick anything you want left alone; ticking a folder ticks everything in it.
  • How files are compared: by size and modified time. Times within 2 seconds count as the same (0–3600 in Settings → General). FTP servers that only list times to the minute always get at least 60 seconds.
    • One-way sync never quietly replaces a newer file: a target that's newer with a different size is listed as Replaces a newer file and starts unticked. Newer with the same size counts as matching, so servers that can't keep file times (plain vsftpd) don't re-upload everything each time.
    • Both ways copies whichever side is newer. Files whose times match but whose sizes differ are listed but not synced, since there's no telling which one to keep.
    • On Windows and macOS, README.md and Readme.md are the same file. Two server names that differ only in case are listed as Can't sync.
  • The copies run in the transfer queue, as one upload and/or one download row labelled Sync …, so they're parallel, pausable and retryable like any transfer.
  • Deletes are off by default, one-way only, and shown in red. They run only after every copy made it, and each target is checked again first: anything that changed since the compare is kept. Links are deleted as links, never followed. A linked folder, a broken link or an unreadable item is left alone on both sides: its counterpart is never deleted, and nothing is copied through the link.
  • Copies re-check their targets too. If a target changed after the compare (a coworker uploaded a new version while the preview was open), it's kept, the file is reported as failed, and nothing is deleted.

Phase 3: power transfers and remote edit

  • Several files at once. Each tab transfers up to 3 files in parallel (1–10 in Settings → General), each on its own connection: an extra channel on the SFTP connection, or another FTP sign-in using the session's credentials, so you're never asked again. Servers that refuse extra connections get fewer workers rather than errors.
  • Pause and resume. Paused files keep what they've transferred and carry on from there where the server supports it. A changed source file starts over instead.
  • Dropped connections. Hatch reconnects and continues each file from where it stopped. If it can't reconnect, the transfer waits ("Waiting for connection") and carries on once you reconnect.
  • Retry a failed transfer: files that made it are skipped, the rest pick up where they stopped. One failed file no longer stops the others.
  • Name clashes: Replace, Skip, Rename (keeps both, as name (1).ext) or Replace if newer, once or for the whole transfer. File times are kept, so Replace if newer means what it says.
  • Edit remote files (F4, or Edit in the right-click menu). Hatch downloads the file to a private folder, opens it in your editor, and uploads every save. If the server's copy changed since you opened it, Hatch asks before replacing it.
    • Without an editor set in Settings, web pages, text and data files open in your system's app for them; anything else opens in a plain text editor (Notepad, TextEdit) so it can't run. On Linux, those other files need an editor set.
    • The local copies are deleted when you close the tab, and when Hatch starts.

Phase 2: more protocols and layout

  • FTP and FTPS, with explicit (AUTH TLS) and implicit (port 990) TLS. Certificates are checked against your system's trust store.
    • An untrusted certificate is shown with its SHA-256 fingerprint, and you choose Trust once or Trust and save, the way host keys work. Saved exceptions are listed in Settings → Certificates.
    • FTPS never falls back to plain FTP, and no password is sent before TLS is up.
  • Plain FTP asks first. Hatch says that passwords and files are sent unencrypted, and a saved site can remember your answer.
  • Security badge. Green for a verified SFTP host key or a trusted certificate, amber for an accepted certificate exception, red for plain FTP.
  • Tabs. Each tab has its own connection and panes, and reconnects on its own. A disconnected tab keeps its last listing, greyed out.
  • Remote-only layout. Hide the local pane; downloads then go to a folder you choose (your Downloads folder by default).
  • Quick connect understands sftp://, ftp://, ftpes:// (explicit TLS) and ftps:// (implicit TLS).
  • Signing in tries your default SSH keys (~/.ssh/id_ed25519, id_ecdsa, id_rsa) when a site has no key set. Cancelling a passphrase prompt moves on to the next method.
  • Cancel a connection that's taking too long.

Phase 1: core SFTP

  • Saved sites. Passwords and key passphrases live only in your OS keychain (Windows Credential Manager, macOS Keychain, Linux Secret Service), never in config files.
  • Hosts from ~/.ssh/config. Includes, wildcards and identity files are understood. Jump hosts aren't supported yet.
  • Strict host-key checking:
    • The first time you connect to a server, Hatch shows its SHA-256 fingerprint and asks you to confirm it.
    • If a server's key changes later, the connection is blocked. Saved keys can be managed in Settings → Known hosts.
  • Sign-in methods: SSH agent (OpenSSH agent or Pageant), key files (OpenSSH, PEM and PuTTY .ppk v2/v3), passwords, and keyboard-interactive prompts (2FA codes).
  • Dual-pane browser. It stays fast with very large folders and has sortable columns, keyboard shortcuts, right-click menus, and drag-and-drop between panes and from Explorer.
  • File operations: rename, delete, new folder and permissions (chmod).
  • Transfers. Folders transfer recursively, with progress and cancel. Hatch asks before replacing a file. Downloads only appear under their final name once complete.
  • Theme: light and dark, following your system unless you choose one.

Keyboard

Key Action
F5 Copy selection to the other pane
F4 Edit a remote file
F2 Rename
F7 New folder
Del Delete
Enter / Backspace Open / go up
Ctrl+L Type a path
Ctrl+R Refresh
Ctrl+A Select all
Tab Switch pane
Ctrl+T / Ctrl+W New tab / close tab
Ctrl+Tab / Ctrl+Shift+Tab Next / previous tab

Size

Phase 6 release build on Windows x64: 5.8 MB NSIS installer. The growth is Partner Share's networking (iroh's QUIC stack). Phase 5 was 4.1 MB NSIS and 5.8 MB MSI; most of that phase's growth was the updater's HTTPS client (Phase 4 was 3.7 MB and 5.3 MB, Phase 3 3.6 MB and 5.2 MB, Phase 2 3.5 MB and 5.0 MB, Phase 1 2.8 MB and 4.0 MB). The Windows WebView2 runtime is provided by the OS.

Development

You need:

  • Rust (stable) and the Tauri prerequisites for your OS.
  • Node.js 22.
  • Docker Desktop, for the integration and E2E tests.
npm install
npm run tauri dev

Debug builds regenerate the TypeScript bindings for the Rust commands in src/lib/bindings.ts.

Build output location. The repository may live in a synced folder such as OneDrive, so .cargo/config.toml points Cargo's build output to C:/Users/ethan/.cargo-target/hatch. Change that path for your machine, or override it with CARGO_TARGET_DIR. CI does this.

Tests

npm test                     # frontend unit tests (Vitest)
npm run check                # svelte-check / TypeScript
cargo test --manifest-path src-tauri/Cargo.toml                         # Rust unit tests
docker compose -f docker/compose.yml up -d --build
cargo test --manifest-path src-tauri/Cargo.toml --features integration  # against the Docker test servers
npm run e2e                  # Windows only: drives a debug build over WebView2 DevTools

The Docker test servers listen on localhost only. They're for tests, never expose them:

Server Port Users
SFTP (atmoz/sftp) 2222 pw / pw, key (fixture keys in src-tauri/tests/fixtures/keys), drop / drop (tests kill its sessions)
FTP (vsftpd) 2121 ftpuser / ftppw
FTPS, explicit TLS (vsftpd) 2122 ftpuser / ftppw
FTPS, implicit TLS (vsftpd) 2990 ftpuser / ftppw
FTP with MLSD (ProFTPD) 2124 ftpuser / ftppw
FTP, two connections per address (ProFTPD) 2125 ftpuser / ftppw

The FTPS certificate is signed by a test CA in docker/ftp/tls. The plain FTP server advertises an unroutable passive address on purpose, so the tests cover servers behind NAT. After changing anything under docker/, run docker compose ... up -d --build again; the tests start missing containers but don't rebuild images. A change to docker/sftp/users.conf needs docker compose -f docker/compose.yml up -d --force-recreate sftp.

Security checks

cd src-tauri && cargo audit && cargo deny check
npm audit --omit=dev

One advisory is knowingly ignored: RUSTSEC-2023-0071, a timing side channel in the rsa crate with no fixed release. Hatch only uses RSA keys to sign SSH sign-in challenges locally and never decrypts with them. The reasoning is in src-tauri/.cargo/audit.toml.

Releases

Pushing a tag like v0.2.0 runs .forgejo/workflows/release.yml on the Forgejo runner (label docker). It builds a .deb, an .AppImage and a Windows NSIS installer (Hatch_*-setup.exe, cross-compiled with cargo-xwin), then attaches them and SHA256SUMS.txt to a release for that tag. Tags with a - (like v0.2.0-rc.1) become pre-releases.

One-time setup:

  1. In Forgejo, open your avatar → Settings → Applications → Access tokens. Create a token with repository: Read and write (write:repository).
  2. In the Hatch repository, open Settings → Actions → Secrets. Add the token as a secret named RELEASE_TOKEN.
  3. Add the update signing key as a secret named TAURI_SIGNING_PRIVATE_KEY (see The update signing key below).

To cut a release:

  1. Bump the version in src-tauri/tauri.conf.json, src-tauri/Cargo.toml and package.json, then commit. The workflow refuses a tag that doesn't match.
  2. Run git tag v0.2.0 && git push origin main v0.2.0.

The NSIS installer and the AppImage are signed for the updater, and each release also gets a latest.json that lists them. Installed copies of Hatch read https://forge.finitedevelopment.com/Finite/Hatch/releases/download/latest/latest.json. Forgejo serves that file from the newest release that isn't a pre-release. Publishing a release is all it takes to update everyone, and pre-releases (v0.2.0-rc.1) don't reach regular installs.

Not built yet: .msi (WiX only runs on Windows, so it needs a Windows runner) and macOS (needs a Mac runner). The installers have no Authenticode signature (that needs a paid certificate), so Windows SmartScreen may warn.

The update signing key

The updater's public key is in src-tauri/tauri.conf.json (plugins.updater.pubkey). The private key is not in the repository:

  • On the development machine it's at C:\Users\ethan\.tauri\hatch-updater.key (no password). Back it up somewhere safe. If it's lost, installed copies can't verify new releases and have to be reinstalled by hand.
  • For releases, add its contents as the Forgejo secret TAURI_SIGNING_PRIVATE_KEY. The release workflow stops if the secret is missing.
  • To sign a local build, set TAURI_SIGNING_PRIVATE_KEY to the key file's path (or its contents) and TAURI_SIGNING_PRIVATE_KEY_PASSWORD to an empty string before npm run tauri build. Without them, the installers still build but tauri build fails at the signing step.

License

MIT