drop is a small local file sharing tool for machines that are reachable through a reverse tunnel
such as Cloudflare Tunnel or Tailscale Funnel.
The intended workflow is explicit:
drop add ~/Desktop/file.md --expires 24h | pbcopy
drop share ~/Drop/file.md --expires 24h | pbcopyThe printed URL includes the filename for trust, but the random token is the only authority:
https://drop-1.rix1.dev/d/n4Ph7xQ9sL/file.md
This is a pragmatic v1:
- explicit shares only; no file watcher
- one configured share root
- random unguessable URLs
- live filesystem checks, so deleted files become unavailable
- direct file downloads
- minimal directory listings
- expiry and revoke support
- basic analytics: access count, download count, bytes sent, IP, user agent
- lock-file protected JSON database for concurrent CLI/server use
- loopback-only server by default
- symlink serving is rejected
- no-cache, no-index, no-sniff, and frame-deny response headers
- no third-party runtime dependencies
- small HTTP serving surface in
src/server.ts; CLI commands, shared storage/path-safety logic, and shared types live separately insrc/cli.ts,src/core.ts, andsrc/types.ts
For local development:
deno task drop help
deno task drop serveAdd this checkout's bin directory to your shell path:
export PATH="/Users/rix1/Development/drop/bin:$PATH"Then configure one machine:
mkdir -p "$HOME/Drop" "$HOME/.config/drop"Create ~/.config/drop/config.json:
{
"root": "~/Drop",
"db": "~/.drop/drop-db.json",
"baseUrl": "https://drop-1.rix1.dev",
"bind": "127.0.0.1:7777",
"maxEvents": 10000,
"maxDirectoryEntries": 1000,
"trustProxyHeaders": "cloudflare",
"allowPublicBind": false
}Smoke test locally:
drop config
drop serveIn another terminal:
curl http://127.0.0.1:7777/health
drop add README.md --expires 10min | pbcopydrop share ~/Drop/file.md --expires 24h
drop add ~/Desktop/file.md --expires 24h
drop add ~/Desktop/file.md --move --expires 24h
drop add ~/Desktop/file.md --name readable-name.md --expires 24h
drop ls
drop stats file.md
drop events file.md
drop revoke file.md
drop serveExpiry values support 10min, 1hr, 24hr, 7d, forever, plus simple durations such as 15m,
2h, and 3d.
Install prerequisites:
brew install deno cloudflaredRun checks before installing:
deno task check
deno task testUse a restricted server command in production:
deno run \
--allow-env=HOME,DROP_CONFIG,DROP_ROOT,DROP_DB,DROP_BASE_URL,DROP_BIND,DROP_TRUST_PROXY_HEADERS,DROP_ALLOW_PUBLIC_BIND \
--allow-read="$HOME/Drop,$HOME/.drop,$HOME/.config/drop" \
--allow-write="$HOME/Drop,$HOME/.drop" \
--allow-net=127.0.0.1 \
src/drop.ts serveKeep bind on 127.0.0.1. drop serve --bind 0.0.0.0:7777 is refused unless --allow-public-bind
is passed.
Point drop-1.rix1.dev at the local server:
cloudflared tunnel login
cloudflared tunnel create drop-imac
cloudflared tunnel route dns drop-imac drop-1.rix1.devCreate ~/.cloudflared/config.yml:
tunnel: drop-imac
credentials-file: /Users/rix1/.cloudflared/drop-imac.json
ingress:
- hostname: drop-1.rix1.dev
service: http://127.0.0.1:7777
- service: http_status:404Run it:
cloudflared tunnel run drop-imacdrop uses CF-Connecting-IP when present, so analytics show the downloader IP behind Cloudflare
Tunnel. Keep the server bound to 127.0.0.1 so that header cannot be spoofed by direct traffic.
Copy the template:
cp examples/dev.rix1.drop.plist "$HOME/Library/LaunchAgents/dev.rix1.drop.plist"Edit paths if deno, the checkout, or your home directory differ:
plutil -lint "$HOME/Library/LaunchAgents/dev.rix1.drop.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/dev.rix1.drop.plist"
launchctl kickstart -k "gui/$(id -u)/dev.rix1.drop"Logs:
tail -f "$HOME/Library/Logs/drop.log" "$HOME/Library/Logs/drop.err.log"For cloudflared, either run cloudflared service install or create a separate launchd service
that runs cloudflared tunnel run drop-imac.
Environment variables override the config file:
export DROP_ROOT="$HOME/Drop"
export DROP_DB="$HOME/.drop/drop-db.json"
export DROP_BASE_URL="https://drop-1.rix1.dev"
export DROP_BIND="127.0.0.1:7777"
export DROP_TRUST_PROXY_HEADERS="cloudflare"trustProxyHeaders can be none, cloudflare, or all. Use cloudflare for Cloudflare Tunnel.
Use all only behind a trusted reverse proxy that strips incoming client-supplied proxy headers.
Directory shares grant access to files under that directory through the same token. The URL path after the display name is still checked against the configured share root before anything is served.
download_count increments on successful full-file GET responses. Range requests are logged and
counted as accesses, but not as complete downloads.