- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| docs | ||
| src/drop | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
⬇ drop
Send a file to a remote server by dragging it into a browser tab.
Run drop on the machine you want to receive the file (SSH session, VPS,
anything Python runs on), it prints a share URL, and anyone who opens that URL
can drag&drop files straight into the folder where you ran the command —
no auth, no client install, no size limit.
dropon the server → printshttps://<uid>.example.com- open the URL from your laptop browser
- drag files → they land in the folder, streamed in 64 KiB chunks
Works on a laptop, a VPS, a Raspberry Pi, a container — with or without Caddy.
Install
cd ~/dev/py/drop
uv sync # stdlib only — zero runtime deps
uv run drop --help
Optional global entrypoint (so drop works from anywhere):
uv tool install . # or: pipx install .
Usage
drop # auto-detect best publication mode, 1h window
drop --hours 3 # 3h window
drop --max-size 2G # reject uploads larger than 2 GiB
drop list # active sessions
drop down <uid|port> # close (graceful: waits for in-flight uploads)
drop down <uid> --force # close now (SIGKILL, no grace)
The printed URL (8 hex chars) is the shared secret — give it to whoever needs
to send you a file. Sessions auto-expire (--hours N, default 1 h) and the
server cleans up its own Caddy route on shutdown.
Publication modes
drop auto-detects the best way to publish the URL. Override with a flag:
| Flag | When | URL | TLS |
|---|---|---|---|
| (auto) | picks the best below | — | — |
--domain D |
host runs Caddy + you own *.D |
https://<uid>.D |
Let's Encrypt via Caddy |
--sslip |
public IP, no domain | https://<uid>.<ip>.sslip.io |
via Caddy if present, else HTTP |
--local |
behind NAT / no public IP | http://localhost:<port> |
via ssh -L tunnel |
--direct |
bind all interfaces, HTTP | http://<ip>:<port> |
none |
Mode --domain
Needs Caddy with admin API on localhost:2019 and a wildcard DNS (*.D)
pointing at the host, plus Let's Encrypt. drop adds an in-memory
reverse_proxy route via the Caddy admin API (no Caddyfile edit, no reload)
and removes it on shutdown. The first hit triggers cert issuance (~3–8 s);
drop probes and prints ✓ TLS prêt.
# set the wildcard domain once, or pass --domain each time
DROP_DOMAIN=example.com drop --hours 1
# → https://<uid>.example.com
Any host running Caddy with a wildcard cert works — not specific to one VPS.
Mode --sslip (public IP, no domain)
Uses the free sslip.io wildcard DNS, which resolves <anything>.<ip>.sslip.io
to <ip>. If Caddy is present, you get real HTTPS (cert on the sslip host). If
not, it binds 0.0.0.0 and serves HTTP directly on a public port (open the
firewall). The public IP is auto-detected via an echo service.
Mode --local (no public IP / NAT)
The server binds 127.0.0.1 only. Forward it through your existing SSH session:
# on the remote host:
drop --local
# prints: http://localhost:57625
# ssh -L 57625:127.0.0.1:57625 <user>@<serveur>
# on your laptop, in another terminal:
ssh -L 57625:127.0.0.1:57625 ubuntu@server
# then open http://localhost:57625
No infra needed — the SSH channel carries it. Most secure option (SSH = auth).
Large files
- No protocol size limit. Uploads stream in 64 KiB chunks; a 10 GiB file uses ~64 KiB of RAM server-side. Caddy (domain/sslip-caddy modes) imposes no body limit by default.
- Graceful shutdown.
drop down(or auto-expiry) waits up to 10 min for in-flight uploads to finish before cutting, so a multi-GB transfer isn't truncated.--forceskips the wait. - No resume. If the connection drops mid-transfer, the upload restarts
from zero. For unstable links + very large files, prefer
crocoroneshot.
How it works
browser ──HTTPS──> Caddy (TLS, route added via admin API) ──> 127.0.0.1:<port> ──> drop server ──> file in $PWD
src/drop/server.py— threaded HTTP server (stdlibhttp.server): serves the drag&drop page, streamsPOST /uploadto disk in chunks, tracks active uploads. On SIGTERM/expiry, waits for uploads then removes the Caddy route.src/drop/caddy.py— minimal Caddy admin API client (stdliburllib).src/drop/cli.py— mode detection, port/uid generation, state, lifecycle.
State: ~/.config/drop/state.json (DROP_STATE to override).
Caddy admin: http://localhost:2019 (DROP_ADMIN to override).
Security
The URL is public during the window (no auth) — the random 8-hex-char uid is
the secret, and the time window bounds exposure, exactly like a PairDrop code.
Don't leave a session open longer than needed. For sensitive long-lived needs,
put it behind Caddy basic_auth in the Caddyfile instead.
Filenames are sanitized to a basename (no path traversal, no clobbering:
duplicate names get .1, .2, …).
Development
This project is stdlib-only (zero runtime dependencies) and uses uv:
uv sync # create/update the venv
uv run drop --hours 1 # run in dev
uv run python -c "import drop.cli, drop.server, drop.caddy" # compile check
Manual smoke test (no formal test suite yet) — cover the three main modes:
uv run drop --domain example.com # or --sslip / --local
# then upload via the printed URL (curl or browser), check md5sum integrity
uv run drop down <uid> # then verify the Caddy route is gone
Before committing: run the compile check + uv run drop --help, and make
sure no test artifacts (*.bin) leak into the commit (covered by .gitignore,
but check git status).
Contributing
See AGENTS.md for the project rules and design principles (also loaded as
CLAUDE.md for agent harness compatibility). Key invariants to preserve:
- Stdlib only —
dependencies = []inpyproject.toml. Discuss before adding any runtime dependency. - Single Python process owns the server + expiry + Caddy route cleanup. No external timer/wrapper scripts.
- Fixed-memory streaming — 64 KiB chunks to disk, never read a whole body into RAM.
- Graceful shutdown — the server waits for in-flight uploads (≤ 10 min) and removes its own Caddy route on exit.
- Launch via
python -m drop.serverso package imports resolve. - The URL is the secret — no auth by default; the random uid + time window are the security boundary (PairDrop model).
License
MIT