ClipRelay is an end-to-end encrypted, relay-based clipboard and file sync tool.
- Sync clipboard text and files up to 50 MiB between devices that share a room code.
- Clients connect outbound over WebSocket (
ws://orwss://) to a relay. - All payloads are encrypted client-side with XChaCha20-Poly1305 — the relay only forwards opaque blobs and cannot decrypt anything.
- Windows clients use an egui/eframe tray-first UI with red/amber/green tray status indicators, a tabbed single-window interface (Send | Options | Notifications), and automatic DPI scaling.
cliprelay/
├─ Cargo.toml # Workspace root (version source of truth)
├─ cliprelay-core/ # Pure core logic (framing, crypto, limits)
├─ cliprelay-relay/ # Relay server (Linux / Windows)
├─ cliprelay-client/ # Windows tray client
└─ deploy/ # systemd unit + installer script
Room key derivation:
IKM = SHA256(room_code UTF-8)salt = SHA256(sorted(device_id list concatenated))room_key = HKDF-SHA256(IKM, salt, info="cliprelay v1 room key", len=32)
Encryption:
XChaCha20-Poly1305- Nonce =
SHA256(sender_device_id)[0..16] || counter_le_u64
Replay protection:
- Receiver tracks latest
counterper sender. - Duplicate/stale counters are rejected.
| Resource | Limit |
|---|---|
| Clipboard text | 256 KiB |
| File transfer | 50 MiB |
| Relay message frame | 300 KiB (files are chunked) |
| Devices per room | 10 |
Requirements:
- Rust stable toolchain
- Client requires Windows (egui/eframe UI with Win32 tray integration)
- Relay runs on Linux or Windows
cargo check
cargo test -p cliprelay-core
cargo test -p cliprelay-relay --test e2e_relay
cargo test -p cliprelay-client- CI (
.github/workflows/ci.yml): Runs on push tomainand PRs —cargo check+ tests. - Release (
.github/workflows/release.yml): Triggered byv*.*.*tags -- buildscliprelay-relay(Linux) andClipRelay.exe(Windows) and attaches them to the GitHub release. - Local release script (
update-application.ps1): Bumps version, builds, tests, commits, tags, pushes, and prunes old releases. Supports-DryRun.
cargo run -p cliprelay-relay -- --bind-address 0.0.0.0:8080Endpoints: /ws (WebSocket), /healthz (health check).
The relay has no room code — it forwards messages within whatever room_id clients connect with.
cargo run -p cliprelay-client -- --server-url wss://relay.swatto.co.uk/ws --room-code my-secret-room --client-name LaptopWhen launched without --room-code, the client shows a Room Choice dialog. Config is saved to %LOCALAPPDATA%\ClipRelay\config.json.
Run a second client with the same room code and a different --client-name to test.
This section covers deploying the relay on a Linux server (e.g. Ubuntu on a cloud VM) with TLS termination via Caddy.
- A Linux server with a public IP and a domain name pointing to it (e.g.
relay.example.com) - Ports 80 and 443 open in your firewall / cloud security list
- Caddy installed for automatic TLS
Option A: Download from GitHub Releases
curl -Lo cliprelay-relay \
https://github.com/Swatto86/cliprelay/releases/latest/download/cliprelay-relay
chmod +x cliprelay-relayOption B: Build from source
# Install Rust if needed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# Clone and build
git clone https://github.com/Swatto86/cliprelay.git
cd cliprelay
cargo build --release -p cliprelay-relay
cp target/release/cliprelay-relay .The repo includes a one-command installer that creates a dedicated service user, installs the binary, and enables the service:
sudo ./deploy/install-relay-systemd.sh \
--binary ./cliprelay-relay \
--bind-address 127.0.0.1:8080This will:
- Create a
cliprelaysystem user (no login shell, no home directory) - Write environment config to
/etc/cliprelay/relay.env - Copy the binary to
/opt/cliprelay/bin/cliprelay-relay - Install and enable the
cliprelay-relay.servicesystemd unit - Start the relay on
127.0.0.1:8080(loopback only — Caddy handles public traffic)
Verify it's running:
sudo systemctl status cliprelay-relay
curl http://127.0.0.1:8080/healthz
# Should return: {"ok":true}The service restarts on failure and starts automatically on boot.
Installer options:
| Flag | Default | Description |
|---|---|---|
--binary PATH |
./cliprelay-relay |
Path to the relay binary |
--bind-address ADDR |
127.0.0.1:8080 |
Listen address |
--rust-log LEVEL |
info |
Log level |
--install-dir DIR |
/opt/cliprelay/bin |
Binary install directory |
Edit /etc/caddy/Caddyfile:
relay.example.com {
handle /healthz {
respond "ok" 200
}
handle /ws* {
reverse_proxy 127.0.0.1:8080
}
}Then reload:
sudo systemctl reload caddyCaddy automatically obtains and renews a TLS certificate via Let's Encrypt. Clients can now connect with wss://relay.example.com/ws.
From a Windows machine:
curl https://relay.example.com/healthz # Should return: ok
cargo run -p cliprelay-client -- `
--server-url wss://relay.example.com/ws `
--room-code my-secret-room `
--client-name MyPCDownload or build the new binary and re-run the installer:
sudo systemctl stop cliprelay-relay
sudo ./deploy/install-relay-systemd.sh --binary ./cliprelay-relayThe systemd unit includes hardening out of the box:
- Runs as dedicated
cliprelayuser (not root) NoNewPrivileges,ProtectSystem=strict,ProtectHome=trueMemoryDenyWriteExecute,LockPersonality, restricted syscalls- Network limited to
AF_UNIX,AF_INET,AF_INET6
| Colour | Meaning |
|---|---|
| Red | Disconnected / cannot reach relay |
| Amber | Connected, but no room key yet (usually the only device in the room) |
| Green | Connected and room key is ready — send/receive enabled |
- Double-click tray icon — toggle the Send window (or use the configurable global hotkey)
- Right-click tray icon — context menu with Quit
- Auto apply — when on, incoming clipboard text is applied automatically; when off (default), a popup lets you Apply or Dismiss
- Start with Windows — adds a per-user startup entry (
--backgroundmode) - Global hotkey — configurable shortcut to toggle the Send window (default: Ctrl+Alt+C)
- Open the Send window (double-click tray or hotkey)
- Type or paste text
- Click Send text
- Open the Send window
- Click Send file… and pick a file (max 50 MiB)
- The file is chunked, encrypted, and sent through the relay
- The receiver gets a popup with a preview and can click Save — files are saved to
Downloads\ClipRelay
- Text: popup shows a preview with Apply to Clipboard / Dismiss (or auto-applied if the option is on)
- Files: popup shows file name and size with a Save button
- Forwards encrypted WebSocket messages between clients in the same room
- Cannot decrypt any payload — encryption is end-to-end
- Does not store clipboard or file history
- Does not persist messages — offline clients miss messages
- Stateless — only in-memory room/device membership
- Full architecture + user guide + cloud ops walkthrough:
docs/HOW_IT_WORKS.md - Project atlas / system map:
PROJECT_ATLAS.md