Skip to content

Repository files navigation

DropLAN

Send a file to the laptop across the room without it leaving the room.

CI status License: MIT Go 1.25

Quick start · How it works · Security · Architecture


DropLAN is a desktop app for moving files between devices on the same local network — no account, no cloud relay, no third-party storage. Devices find each other over LAN multicast and broadcast, and the file travels directly between the two machines over TLS.

Status: MVP. The end-to-end flow works on macOS and Windows. Transfers run over TLS 1.3 and peers are identified by key fingerprint, but trust is established by a human comparing that fingerprint — there is no pairing database yet. See Security.

How it works

  Device A                                            Device B
  ┌────────────────────────┐                   ┌────────────────────────┐
  │  Tauri shell (Rust)    │                   │  Tauri shell (Rust)    │
  │  ── UI, history,       │                   │                        │
  │     accept/reject      │                   │                        │
  ├────────────────────────┤                   ├────────────────────────┤
  │  droplan-core (Go)     │                   │  droplan-core (Go)     │
  └────────────────────────┘                   └────────────────────────┘
             │                                            │
             │   UDP 7332 · multicast + broadcast         │
             │   "I am MacBook, reach me at ...:7331"     │
             │◄──────────────────────────────────────────►│
             │                                            │
             │   TCP 7331 · TLS 1.3 · offer → accept →    │
             │   payload                                  │
             └───────────────────────────────────────────►│

The desktop shell never speaks the wire protocol itself. It supervises the Go core as a child process and exchanges newline-delimited JSON with it over stdin/stdout — progress events flow up, accept/reject decisions flow down. That split keeps the transfer logic testable without a GUI and keeps the GUI replaceable without touching the protocol.

A transfer is one file over one TLS connection:

  1. Sender writes an offer (protocol, fileName, fileSize, and the SHA-256 checksum of the payload).
  2. Receiver validates it against its size limit, shows it in the UI, and waits for a human decision.
  3. On accept, the receiver acknowledges and the sender streams the payload.
  4. The receiver hashes the payload as it writes it to a temporary file and discards the transfer if the digest does not match the offer.
  5. Only then does it atomically claim a free name and rename the payload into place — an interrupted transfer never leaves a partial file under the final name, and two peers sending report.pdf at the same moment get two distinct files.

Files never pass through the UI process: the drop zone hands the core a filesystem path, and the core opens it directly. Dropping a 4 GB file costs the UI nothing.

Full details in docs/architecture.md.

Repository layout

cmd/droplan-core/   Go CLI: manifest, diagnostics, discover, receive, send
core/discovery/     LAN peer discovery (multicast + broadcast heartbeats)
core/connection/    TLS listen/dial helpers and fingerprint pinning
core/identity/      Per-device certificate and key fingerprint
core/transfer/      Wire protocol, progress reporting, accept/reject flow
internal/app/       Build metadata stamped into the binary
internal/cli/       Confirmation policies and progress output for the CLI
storage/history/    Placeholder for Go-owned history persistence
ui/                 Tauri desktop shell (Rust) and frontend (TypeScript)
docs/               Architecture and release notes

Try it without the GUI

The Go core is a self-contained CLI, which is the fastest way to see the protocol work. In one terminal:

go run ./cmd/droplan-core receive --listen :7331 --dir ./received

In another:

echo "hello from the other terminal" > example.txt
go run ./cmd/droplan-core send --to 127.0.0.1:7331 --file ./example.txt

The receiver prompts for confirmation; answer y and the file lands in ./received.

Other commands:

go run ./cmd/droplan-core manifest                       # build metadata as JSON
go run ./cmd/droplan-core diagnostics                    # what the LAN looks like from here
go run ./cmd/droplan-core discover --device-name MacBook # watch peers appear and expire

Useful receive flags:

Flag Purpose
--auto-accept Skip the confirmation prompt
--confirm-json-stdin Take accept/reject decisions as JSON lines (how the desktop shell drives it)
--progress-json Emit machine-readable progress
--max-file-size Reject offers above N bytes (0 = unlimited)
--max-sessions Cap concurrent transfers (default 8)
--idle-timeout Fail a stalled session after this long (default 30s)
--confirm-timeout Decline an offer nobody answered within this long (default 2m)
--identity-dir Where this device's TLS key lives (defaults to the user config dir)

Run the desktop app

cd ui
npm install
npm run tauri dev

The dev build compiles the Go core automatically via build.rs, so Go, Node, and a Rust toolchain all need to be installed.

Development

go test ./... -race        # Go core
gofmt -l .                 # formatting
golangci-lint run ./...    # linting
cd ui && npm run build     # type-check and bundle the frontend
cd ui/src-tauri && cargo fmt --check && cargo clippy --all-targets -- -D warnings

CI runs all of these on every push and pull request. The Go job runs on Linux, macOS, and Windows; linting, the frontend build, and the Rust checks run on Linux only.

Building a distributable app

cd ui
npm install
npm run build:desktop            # host platform
npm run build:desktop:windows    # Windows portable (run on Windows)

Output:

Target Artifact
macOS ui/src-tauri/target/release/bundle/macos/DropLAN.app (+ DropLAN-macos.zip)
Windows ui/src-tauri/target/release/portable/DropLAN.exe (+ portable zip)

Both bundles embed the compiled droplan-core binary, so the target machine needs neither Go nor the source tree. On Windows the core is extracted into local app data on first launch. Portable Windows builds assume the WebView2 runtime is already present; use npm run build:desktop:windows:installer if you need a build that can provision it.

For signing and notarization, see docs/release-signing.md.

Releases

The Desktop Release workflow (manual dispatch) builds both desktop targets and publishes them as GitHub Release assets. Inputs: ref (usually main), tag (e.g. v0.1.0), optional release_name and notes, and draft — keep draft enabled while validating the assets. Rerunning for an existing tag refreshes the assets with --clobber rather than creating a second release.

The Desktop Artifacts workflow builds the same targets without publishing, which is the easy way to get a Windows build from a Mac.

Security

Every device generates a self-signed Ed25519 certificate on first run and is identified by the SHA-256 fingerprint of its public key. Transfers run over TLS 1.3 with certificates required from both sides, so:

  • Payloads and file names are encrypted on the wire.
  • The receiver sees the sender's fingerprint next to the approval prompt.
  • A sender that knows the receiver's fingerprint pins it, and the handshake fails if another host answers on that address.
  • Payloads carry a SHA-256 digest verified before the file is kept.
  • Concurrent sessions and file size are capped, and oversized offers are refused during the handshake.

Fingerprints are shown as the first eight bytes, grouped: C745 DCA0 45FB AF7E.

What is still missing:

  • No pairing memory. Fingerprints are shown and can be pinned, but nothing remembers "I trust this device", so the guarantee is only as good as the user actually comparing the short fingerprint the first time.
  • Discovery is unauthenticated. A host can advertise any device name. It cannot claim another device's fingerprint, so a sender that pins the advertised fingerprint reaches the intended device or fails — but only a user who checks will notice a wrong name.
  • Key file permissions are POSIX-only. On macOS and Linux the device key is written 0600 and received files 0600 in a 0750 directory. Windows has no POSIX mode bits: there the key is protected only by the ACL it inherits from its directory, which is weaker than the guarantee on the other platforms.

Roadmap

  • TLS between peers with a per-device key, and a short fingerprint shown in the approval dialog
  • Remember trusted devices so a fingerprint change is flagged loudly
  • SHA-256 in the offer, verified on receipt
  • Stream files by path instead of buffering them through the UI process
  • Limits on concurrent sessions and file size
  • Multi-file and folder transfers
  • Resumable transfers
  • Move transfer history into Go behind storage/history

Testing on real devices

License

MIT

About

Direct device-to-device file transfer over your local network. TLS 1.3, peer discovery, no account and no cloud relay.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages