Skip to content

Releases: aeroxy/drift

drift v0.4.2

19 May 17:37

Choose a tag to compare

Improved transfer atomicity, staging of all temporary files inside the hidden .drift/ directory, and enhanced end-to-end error propagation / atomic rollback for encrypted WebSocket transfers.

Highlights:

  • Unified Temporary Staging: Staged all in-progress files (both regular files and directory archives) in the hidden .drift/ directory, preventing clutter in the user's browse directory during active transfers.
  • Redundant Rename Mitigation: Added path checking in ChunkedWriter::finalize() to bypass tokio::fs::rename() calls when staging path and final destination coincide (e.g., directory archives).
  • Consolidated Error Routing: Designed a clean TransferError routing pipeline through AppState to propagate network/decompress/write failures back to client/server connection task handlers.
  • Atomic Rollback: Implemented all-or-nothing rollback in TransferReceiver that concurrently sweeps away partial archives and successfully finalized files on any decompression or writer failure.
  • Flattened Match Blocks: Simplified nested timeout pattern match constructs in browser_transfer.rs to flatten channel receiver handling and significantly improve readability.

1. High-Level Summary (TL;DR)

  • Impact: Medium - Significantly improves correctness, file system cleanliness, and failure safety across the transfer lifetime, with zero protocol-breaking changes.
  • Key Changes:
    • Staging & Storage: Migrated active file writes from the target directory to .drift/ staging files, introducing ChunkedWriter::create_with_temp for custom temp paths.
    • Reliability & Errors: Integrated error tracking into oneshot completion channels and introduced AppState::handle_transfer_error to handle TransferError control messages.
    • Decomposition & Refactoring: Flattened nested result checks and consolidated file deletion into a robust concurrent helper TransferReceiver::remove_files.

2. Visual Overview (Code & Logic Map)

Server Core Transfer Error-2026-05-19-173055

3. Detailed Change Analysis

Staging & Writer Updates

  • What Changed: Added ChunkedWriter::create_with_temp to write to a temp file and rename on finalization. Restructured finalize() to skip the rename when the temp path matches the final path.
  • Source: src/fileops/writer.rs
Old Method New Method Reason
ChunkedWriter::create(path) ChunkedWriter::create_with_temp(temp_path, final_path) Allows arbitrary temp directories (e.g., hidden .drift/) to stage writes away from final destination.
Always call tokio::fs::rename Call only if temp_path != final_path Prevents redundant rename and permission/filesystem issues for directory archives located within .drift/.

Robust Error Signaling & Rollback

  • What Changed: Redesigned the oneshot completion mechanism to transmit Result instances rather than empty (). Added a concurrent remove_files helper that performs a complete rollback of the transfer state (temp and finalized files) on failure.
  • Source: src/server/transfer_receiver.rs
Before After Reason
completion_tx: Option<oneshot::Sender<()>> completion_tx: Option<oneshot::Sender<Result<u64, String>>> Signals error message and total bytes written to the waiting transfer handler.
Manual inline tokio futures for archive deletion Concurrent remove_files(Vec<PathBuf>) Generalizes rollback across directory and regular multi-file transfers.

Flattened Completion Checks

  • What Changed: Flattened deep nesting within handle_browser_transfer and push_entries timeout completion handlers.
  • Source: src/server/browser_transfer.rs
// Before (Nested Match)
match tokio::time::timeout(...) {
    Ok(Ok(())) => { ... }
    Ok(Err(_)) => { ... }
    Err(_) => { ... }
}

// After (Flattened Match)
match wait_result {
    Err(_) => { ... }
    Ok(Err(_)) => { ... }
    Ok(Ok(Err(error))) => { ... }
    Ok(Ok(Ok(total_bytes))) => { ... }
}

4. Impact & Risk Assessment

  • Breaking Changes: None. The core wire control protocols and API definitions remain structurally backwards compatible.
  • Testing Suggestions:
    • Directory Rollback Failure Case: Initiate a directory transfer and inject a decompression error (e.g., corrupt tarball signature) halfway. Verify that .drift/ temp archives AND any already extracted regular files are deleted automatically.
    • Simultaneous Multi-file Write: Send multiple files concurrently over WebSocket. Kill the connection abruptly and ensure signal_error() successfully drops all writers and cleans up .drift/ files.

5. Known Issues

Missing cleanup of successful files on partial finalization failure

  • Severity: Low
  • Section: src/server/transfer_receiver.rs - finalize_transfer
  • What's wrong: If a writer fails to finalize in the middle of the loop, successfully finalized files (prior index) are scheduled for deletion, but the current failing writer's final path is also added to dest_paths. Since finalization failed, the file remains in temp_path, which is cleaned up. However, there's a tiny window where partial finalized destination paths are cleaned up correctly but could benefit from a defensive try-catch block.
  • Verdict: The implemented rollback logic is already very robust and handles this well. No immediate action required.

drift v0.4.1

10 May 06:42

Choose a tag to compare

File Sorting and Resizing-2026-05-10-064641

Frontend enhancements

The file list now feels much more like a desktop file manager:

Sortable columns

  • Click on Name, Size, or Date column headers to sort the list
  • Click again to toggle between ascending and descending order
  • Directories always stay at the top, regardless of the sort key

Resizable columns

  • Hover over the divider between Size/Date headers to reveal a draggable handle
  • Drag left or right to adjust column width
  • Minimum and maximum width constraints keep the layout usable

Under the hood

  • Added useColumnResize custom hook
  • No backend changes – the backend still sorts by directories-first alphabetical as a fallback, but the frontend now overrides sorting on the client side

drift v0.4.0

09 May 03:06

Choose a tag to compare

Multi-file transfers in a single request

The web panel's multi-select (Cmd/Ctrl-click, Shift-click) now completes in one round trip instead of one per file. A new V2 data frame format adds a file_index field so chunks for different files can be interleaved over the same WebSocket connection and routed to the correct writer on arrival.

Protocol versioning

Peers now advertise a protocol_version during the handshake. Drift 0.4.0 speaks version 2; it falls back to V1 frames automatically when talking to an older peer, so existing setups keep working without any config changes.

Client-Server Data Transfer

CLI tools no longer interrupt active connections

Running drift ls or drift pull against a server that already has a peer connected would previously clobber the server's connection state, causing the browser panel to lose its remote. The server now saves and restores the persistent connection around each transient CLI session.

Bug fixes

  • Archive filenames for directory entries now include the file index, preventing corruption when multiple folders are sent in one transfer.
  • The finalize log correctly reports the number of files written (was always 0).
  • The connection fingerprint shown in the browser is preserved after a CLI tool disconnects from the same server.
  • recv_control_with_replies is bounded to 100 iterations, preventing a hang if a peer spams InfoRequest or Ping.

drift v0.3.0

07 May 17:04

Choose a tag to compare

Multi-select in the web panel

Pick more than one file at a time.

The Copy buttons in the toolbar now show the running count, and a small "×" appears next to each side so you can clear the selection without touching the file list.

Behind the scenes nothing changes on the wire — drift's protocol has always taken entries: Vec<TransferEntry> per TransferRequest. The web panel now actually fills that array. Each folder in a multi-selection is still compressed to its own .drift/{name}.tar.gz and decompressed individually on the receiver, so per-folder progress and integrity stay intact (no monolithic top-level archive).

drift v0.2.0

06 May 05:36

Choose a tag to compare

Reconnect after remote drop

When the server-to-server connection drops unexpectedly, drift now shows a Reconnect button in the toolbar — no modal, no re-typing credentials.

# Machine A
drift --port 8000

# Machine B — target and password are remembered
drift --port 9000 --target 192.168.0.2:8000 --password secret

If Machine A restarts or the network blips, the Reconnect button appears on Machine B's UI. Click it to re-establish the encrypted session instantly.

Works for both CLI-launched sessions (--target at startup) and connections made through the "Connect to remote" modal — credentials are retained in the server process across drops. An explicit disconnect (the unplug button) clears them, so Reconnect only appears when it makes sense.

New endpoint for scripted use:

curl -X POST http://localhost:9000/api/reconnect
# {"success":true,"fingerprint":"a1b2c3"}

GET /api/info now includes can_reconnect and last_target fields.

drift v0.1.7

19 Apr 07:30

Choose a tag to compare

drift v0.1.7

wss:// — Connect over TLS

drift now connects to remote servers over TLS. Pass a wss:// target to any command that takes --target:

drift --target wss://example.com/drift
drift ls --target wss://example.com/drift
drift pull --target wss://example.com/drift somefile.txt

Bare host:port still works as before (defaults to ws://). Path prefixes are supported for reverse-proxy subpath mounts — wss://example.com/drift automatically resolves to wss://example.com/drift/ws.

Add --allow-insecure-tls to skip certificate verification for self-signed or lab certs.

--disable-ui — Safe public exposure

drift --port 8000 --disable-ui

Strips the REST API (/api/*) and embedded frontend from the router — only /ws is mounted. Use this when running drift behind a public reverse proxy. The encrypted WebSocket handshake is the auth boundary; no unauthenticated filesystem-listing endpoints are reachable.

Example caddy config:

handle_path /drift/* {
    reverse_proxy localhost:8000
}

--daemon — Background server

drift --port 8000 --daemon
# drift daemon started (PID: 12345)
# Logs: /path/to/cwd/drift.log

Spawns the server in the background, detaches from the terminal's process group (so closing the shell doesn't kill it), and appends logs to ./drift.log in the current directory. Kill with kill <PID>.

Simplified CLI

The serve subcommand is removed. Start the server with just drift:

drift --port 8000
drift --port 8000 --target wss://remote.example.com/drift --disable-ui --daemon

drift v0.1.6

18 Apr 10:54

Choose a tag to compare

UI

Connect to a remote from the browser.

You no longer need to restart the server with --target to establish a peer connection. Click "Connect to remote" in the toolbar, enter the address (and optional password), and drift performs the encrypted handshake in place. The connection fingerprint appears inline for MITM verification.

Disconnect or switch to a different remote at any time with the unplug button — no restart required.

Dynamic port.

drift serve now works without --port. When the flag is omitted, the OS assigns a free port and drift logs the full address on startup.

Distribution

Homebrew formula.

Formula/drift.rb is included in the repo. Once a tap is set up:

brew tap aeroxy/drift
brew install drift

Developer

make update-formula

After uploading a release zip, run make update-formula to fetch the live archive, recompute its SHA256, and patch Formula/drift.rb automatically. The existing bump-patch / bump-minor / bump-major targets now keep the formula version in sync alongside Cargo.toml and App.tsx.

drift v0.1.5

14 Apr 12:57

Choose a tag to compare

UI

Tab completion in the path bar.

Pressing Tab while the autocomplete dropdown is open fills the highlighted suggestion (or the first one if none is selected) into the input and appends a trailing /. The debounce effect fires immediately, so the dropdown refreshes to show the contents of the completed directory — ready for the next segment of the path.

This matches the behaviour of most address bars and shells: Tab completes without committing, Enter navigates.

Developer

make bump-patch / bump-minor / bump-major

Three new Makefile targets for version management. Each reads the current version from Cargo.toml, increments the appropriate component, and writes the new version to both Cargo.toml and the frontend version badge in App.tsx.

drift v0.1.4

13 Apr 14:57

Choose a tag to compare

UI

Editable path bar with autocomplete in the file browser.

The path bar in each pane now works like an address bar — click it to type a path directly instead of clicking through directories one by one.

Autocomplete

  • Local pane: as you type, drift fetches the parent directory via /api/browse (debounced 200ms) and shows matching subdirectories in a dropdown. Typing a trailing / shows all subdirectories at that level.
  • Remote pane: suggestions come from the directory listing already on screen — no extra round-trips over the WebSocket connection.

Both panes support full keyboard navigation: arrow keys to move through suggestions, Enter to navigate, Escape to cancel. Clicking a suggestion navigates immediately.

Error handling

Typing an invalid or non-existent path shows a red error toast and reverts the path bar to the previous directory — no broken state left behind.

  • Local: uses the HTTP response code from /api/browse to detect failure
  • Remote: listens for the Error message from the WebSocket and reverts

Testing

  • Four new integration tests: absolute-path browse via REST, non-existent path returns non-OK via REST, absolute-path BrowseRequest via WebSocket, non-existent path returns Error via WebSocket

drift v0.1.3

11 Apr 03:54

Choose a tag to compare

Security

MITM protection via password authentication and connection fingerprints.

Pure X25519 ECDH is fast and private, but without identity verification any attacker who can intercept the WebSocket connection can complete independent handshakes with both sides. v0.1.3 addresses this with two complementary mechanisms.

Password authentication (--password)

When both sides are started with --password <secret>, the handshake includes a challenge-response step after key exchange:

  1. Server generates a random 32-byte nonce and sends AuthChallenge { nonce }
  2. Client computes HMAC-SHA256(password, nonce || shared_secret) and sends AuthResponse { proof }
  3. Server verifies the proof before sending HandshakeComplete

Because the proof covers the DH shared secret, an attacker doing MITM gets a different shared secret on each side and cannot forge a valid proof without knowing the password. Wrong or missing passwords are rejected with a clear error.

Connection fingerprint (always on)

After every handshake, both sides independently compute SHA-256(shared_secret)[0..3] — a 6-character hex string. It is:

  • Logged in both terminals: Handshake complete (fingerprint: a3f2b1)
  • Shown in the web UI toolbar in amber next to the connection status badge

Users can compare the fingerprint out-of-band (Telegram, phone call, etc.) to confirm no one is in the middle — even without a password.

Testing

  • Added password option to DriftProcess integration test helper
  • Three new test cases: correct password connects, wrong password is rejected, missing password is rejected