LinuxDrop does two things between your phone and computer — in real time, end-to-end encrypted, with no cloud account and no vendor in the middle:
- 🔄 Clipboard sync — copy a link, an OTP, a paragraph, or an image on one device and it's instantly ready to paste on the other.
- 📂 Direct file transfer — on any network. Send any file or photo straight device-to-device, whether the two are on the same Wi-Fi or on completely different networks (phone on mobile data, laptop across town). The bytes go peer-to-peer — full speed on a LAN, hole-punched directly over the internet otherwise — and never pass through a server.
The relay that connects your devices is one you host yourself.
You run your own server. There is no built-in/default relay — nothing transits anyone else's infrastructure. Stand one up in a single command (see Self-hosting).
- 🔄 Clipboard sync — text and images. Copy here, paste there, automatically (a screenshot on your phone is ready to paste on your desktop).
- 📂 Direct P2P file transfer — works across any network. Files stream device-to-device over WebRTC: directly even when the two devices are on different networks (NAT hole-punched via STUN, no port-forwarding), and at full speed when they share a LAN. The bytes never touch the relay. Send from the Android Share sheet (or a device's send button), or right-click → "Send with LinuxDrop" on Linux.
- 🔒 End-to-end encrypted — AES-256-GCM with a key derived from a secret only your devices know.
- 🕵️ Zero-knowledge relay — the server only sees an opaque room id; it can't read your clipboard, filenames, or device names, and file bytes never reach it.
- 🔋 Battery-friendly & event-driven — no polling. On Android the clipboard is read in the
background via Shizuku (no root); on Linux via
wl-clipboard. - 🕘 History — recent clipboard items and transferred files are kept locally (encrypted); tap a clip to re-copy it, or a received file to open it.
- 📷 QR pairing & multi-device — share a network by QR; many devices can join the same one.
- 🔁 Resilient — auto-reconnects on network changes; survives sleep/roaming.
- 🛡️ Privacy by default — clipboard content flagged sensitive (OTP fields, password managers) is skipped.
Android (Kotlin / Shizuku) Linux (Go / wl-clipboard)
│ │
│── clipboard (tiny E2E frames) ─► your relay ◄─ clipboard ──│
│ room = hash(secret) │
└────────── files: direct P2P (WebRTC, any network) ────────┘
Clipboard frames are AES-256-GCM end-to-end. Files stream straight between the two
devices on any network — LAN-direct, or hole-punched over the internet via STUN —
and never go through the relay.
A secret (32 random bytes) defines a sync network. Its hash becomes the roomId the relay routes
by; an HKDF of it becomes the AES key your devices encrypt with. The relay is a thin, stateless
pub/sub that never sees the secret — it relays encrypted clipboard frames and the small WebRTC
signaling, while file bytes go peer-to-peer. Full spec: proto/PROTOCOL.md.
You need a small box with Docker and a domain (or a Tailscale/WireGuard address). The bundled compose
runs the relay behind Caddy, which provisions a Let's Encrypt certificate automatically — so
wss:// works out of the box.
git clone https://github.com/burakgon/linuxdrop.git
cd linuxdrop/backend
# Point your domain's DNS at the host, then:
LINUXDROP_DOMAIN=relay.yourdomain.com docker compose up -d --buildThat's it — your relay is live at wss://relay.yourdomain.com. Use that URL when you set up the first
device; other devices receive it automatically from the pairing QR.
- No public domain? Run it on a Tailscale/WireGuard network and use
ws://<private-ip>:3000. - Cross-network transfer just works over STUN hole-punching — no port-forwarding. For the strictest NATs, flip on the bundled TURN relay (just below).
- Already running nginx? See the advanced path in
backend/README.md(docker-compose.prod.yml+deploy/nginx-relay.conf.example). - Verify it:
bun scripts/relay-check.ts wss://relay.yourdomain.com.
Direct hole-punching (STUN) already carries file transfers across most networks. For the rare
symmetric-NAT / locked-down mobile case, the compose bundles a TURN relay (coturn) — opt in with a
profile, so a plain docker compose up stays STUN-only and opens no extra ports:
export TURN_SECRET=$(openssl rand -hex 32) # shared by the relay and coturn
export TURN_URL=turn:relay.yourdomain.com:3478
LINUXDROP_DOMAIN=relay.yourdomain.com docker compose --profile turn up -d --buildOpen UDP 3478 and 49160-49200 in your firewall (on clouds with 1:1 NAT — GCP/AWS/Azure — also add
--external-ip=<public-ip> to the coturn command). Clients then fetch short-lived TURN credentials
from /ice automatically — no client-side change needed.
Linux (KDE/Wayland):
cd linux
bash install.sh # builds linuxdropd → ~/.local/bin, installs the tray app, systemd unit,
# and the "Send with LinuxDrop" right-click action
linuxdropd pair <linuxdrop://… | hex> wss://relay.yourdomain.com # pair to your relay
linuxdropd qr # show a QR for your phone to scan
systemctl --user enable --now linuxdrop # start syncing (system tray)Android:
- Install the APK (grab it from Releases or build
it — see
android/README.md) and install Shizuku. - Open the app → finish the guided Shizuku step.
- Enter your relay URL and Create network, or Scan QR from another device.
Sending files
- Android: Share → LinuxDrop from any app, or tap the send icon next to a device. Received files land in Downloads and show in the in-app history (tap to open).
- Linux: right-click a file in Dolphin → "Send with LinuxDrop", or the tray's "Send file…"
(
linuxdropd send [--to <device>] <file>…from the terminal). Received files land in~/Downloadsand the folder opens automatically.
- The secret never reaches the relay. Pairing is offline (QR /
linuxdrop://link / hex). roomId = base64url(SHA-256(secret))[:32];encKey = HKDF-SHA256(secret, …); clipboard payloads are AES-256-GCM with a fresh random IV each time. A wrong secret fails the GCM tag → rejected.- File transfer is peer-to-peer (WebRTC, DTLS-encrypted). The WebRTC offer/answer is itself sealed with your key before it crosses the relay, so the relay can't MITM the connection — and the file bytes never reach it.
- The relay stores only the last encrypted clipboard frame per room (reconnect catch-up) and short-lived encrypted blobs for clipboard images (room-scoped, ~30 min TTL). It can decrypt none of it.
- Cross-language crypto is pinned by
proto/crypto-test-vectors.json.
| Component | Stack | Build |
|---|---|---|
backend/ |
Bun + Hono + SQLite | bun install && bun test · docker compose up -d --build |
linux/ |
Go (+ wl-clipboard, pion/webrtc) |
go build ./... && go test ./... |
android/ |
Kotlin + Compose + Shizuku + WebRTC | bash scripts/build-apk.sh (hermetic Docker build) |
Copyright 2026 burakgon — licensed under Apache-2.0. The reflective IClipboard access
pattern on Android is inspired by scrcpy (Apache-2.0).


