A simple file share used to generate links to share files for a short time between people without worrying about data collection as the files are discarded after some time.
  • Rust 64.9%
  • JavaScript 28.4%
  • Dockerfile 5.6%
  • HTML 1.1%
Find a file
2026-06-21 21:43:08 -05:00
single-use-file-share-api Centralized router. 2026-06-11 23:52:48 -05:00
single-use-file-share-webapp Removed uneccessary comments. 2026-06-21 21:43:08 -05:00
.dockerignore Finish project: single-image Dockerfile, health endpoint, cleanup, docs 2026-06-11 15:37:05 -05:00
.gitignore Finish project: single-image Dockerfile, health endpoint, cleanup, docs 2026-06-11 15:37:05 -05:00
DOCKER_TODO.md Finish project: single-image Dockerfile, health endpoint, cleanup, docs 2026-06-11 15:37:05 -05:00
Dockerfile Centralized router. 2026-06-11 23:52:48 -05:00
Jenkinsfile Fix stray closing brackets in Jenkinsfile 2026-06-11 23:34:37 -05:00
LICENSE Initial commit 2026-05-31 15:41:50 -05:00
README.md Centralized router. 2026-06-11 23:52:48 -05:00

single-use-file-share

Share a file with a link that stops working after a while. Upload a file, get a link, send it to someone — the file is deleted automatically when it expires, so nothing hangs around to be collected.

  • Backend: Rust (axum + tokio). Streams uploads/downloads in chunks (100 GiB cap), guards disk space, and runs a background janitor that deletes expired files. In production it also serves the built frontend, so the whole app is one process on one port.
  • Frontend: React (Vite + Tailwind). Drop a file, copy the link; the download page shows the file's real name, size, and time left.
  • Storage: file bytes on disk (UPLOAD_DIR), metadata in memory. A restart wipes the metadata (links die; orphaned blobs remain until you clear the volume) — moving metadata to SQLite is the natural next step.

Run with Docker (single image)

docker build -t single-use-file-share .
docker run --rm -p 3000:3000 -v sufs_uploads:/data single-use-file-share

Open http://localhost:3000. Uploaded bytes persist in the sufs_uploads volume.

Mapping the data path to an external volume

Inside the container the app always writes uploads to /data (the image sets UPLOAD_DIR=/data), so that one mount point is all you need to map. Either hand it a Docker-managed named volume, or bind-mount any host folder — e.g. a big external drive:

# Docker-managed named volume (shown above)
docker run --rm -p 3000:3000 -v sufs_uploads:/data single-use-file-share

# Bind mount a host folder (Linux/macOS)
docker run --rm -p 3000:3000 -v /mnt/bigdisk/file-share:/data single-use-file-share

# Bind mount a host folder (Windows, PowerShell)
docker run --rm -p 3000:3000 -v D:\file-share-data:/data single-use-file-share

With the 100 GiB upload cap, make sure whatever you map has room: the server rejects uploads (HTTP 507) rather than let free space on the upload volume drop below 1 GiB. Note that named volumes live inside Docker's own storage (on Docker Desktop, the Linux VM's virtual disk), so for uploads this large a bind mount to a roomy drive is usually the better fit.

Local development

Two terminals (requires Rust and Node):

# 1 — API on :3000
cd single-use-file-share-api && cargo run

# 2 — frontend on :5173 with hot reload; /api/* is proxied to :3000
cd single-use-file-share-webapp && npm install && npm run dev

Open http://localhost:5173.

To test the production setup (Rust serving the built frontend on one port):

cd single-use-file-share-webapp && npm run build
cd ../single-use-file-share-api
STATIC_DIR=../single-use-file-share-webapp/dist cargo run   # → :3000

Configuration (env vars)

Variable Default Meaning
BIND_ADDR 0.0.0.0:3000 Address/port the server listens on
UPLOAD_DIR ./uploads Where uploaded file bytes are written
STATIC_DIR ./static Built frontend the server hands out
FILE_TTL_SECONDS 86400 (24 h) How long a file lives before expiring
CLEANUP_INTERVAL_SECONDS 60 How often the janitor sweeps for expired files

API

All endpoints live under /api; every other path serves the React app.

Endpoint Method Purpose
/api/upload POST Multipart upload (file field) → { "id": "<uuid>" }
/api/files/{id} GET Metadata JSON (name, size, expiry); 404 if unknown/expired
/api/download/{id} GET The file itself, as an attachment; 404 if unknown/expired
/api/health GET Liveness check → { "status": "ok" }