beam

File transfer service via http. Upload with curl or a browser, share the returned URL, and the file is deleted automatically once it expires.

Usage

Upload a single file with PUT; the response body is the download URL:

curl --upload-file myfile.txt https://beam.example.com/myfile.txt
# https://beam.example.com/x123abcx/myfile.txt

Upload one or more files as a multipart form. This is the endpoint the web UI uses, and it responds with JSON:

curl -F 'files=@a.txt' -F 'files=@b.txt' https://beam.example.com/upload
# {"urls":["https://beam.example.com/x123abcx/a.txt", "..."]}

If some parts could not be stored, their names are returned alongside the successful URLs:

{"urls": ["https://beam.example.com/x123abcx/a.txt"], "failed": ["b.txt"]}

Errors use a consistent shape:

{"error": "Upload exceeds maximum size of 5368709120 bytes"}

Download a file, or just open the URL in a browser:

curl https://beam.example.com/x123abcx/myfile.txt -o myfile.txt

Downloads support range requests, so an interrupted transfer can be resumed:

curl -C - -O https://beam.example.com/x123abcx/myfile.txt

Uploaded content is served with X-Content-Type-Options: nosniff and Content-Security-Policy: sandbox, so an uploaded HTML or SVG file opens as a sandboxed, script-free document instead of running on the beam origin.

Endpoints

Method Path Purpose
GET / Web UI with usage instructions
PUT /{filename} Upload one file, returns the URL as text
POST /upload Multipart upload, returns JSON
GET/HEAD /{id}/{filename} Download, supports Range
GET /healthz Liveness/readiness probe

Configuration

Configuration is via environment variables only. Invalid values are rejected at startup.

Variable Default Description
UPLOAD_DIR /uploads Where uploaded files are stored.
PORT 8080 Port to listen on.
FILE_DURATION 168h How long a file is kept. Set to 0 to disable cleanup entirely.
DRYRUN_CLEAN true When true, cleanup only logs what it would delete. See the note below.
CLEANUP_INTERVAL 6h How often the cleanup sweep runs.
MAX_UPLOAD_SIZE 5368709120 Maximum bytes per request (5 GiB), covering all files in one upload.
SHUTDOWN_GRACE 15s How long to wait for in-flight requests on SIGTERM.

DRYRUN_CLEAN defaults to true. Until you set DRYRUN_CLEAN=false, expired files are never actually deleted — the service only logs Dry run: Would remove expired file ... while the disk keeps filling up.

Durations use Go's time.ParseDuration syntax (30m, 12h, 168h). MAX_UPLOAD_SIZE is a plain byte count.

Notes on behaviour worth knowing:

  • MAX_UPLOAD_SIZE limits the whole request, so a multipart upload of several files is measured by their combined size, not per file.
  • A single multipart request may contain at most 100 files.
  • Expiry is based on the file's modification time; downloading does not extend a file's life.
  • SHUTDOWN_GRACE should be aligned with the pod's terminationGracePeriodSeconds. If an upload is still running when the grace period elapses, remaining connections are closed and the process still exits cleanly.

Running

docker run -p 8080:8080 \
    -e DRYRUN_CLEAN=false \
    -e FILE_DURATION=168h \
    -v /srv/beam-uploads:/uploads \
    docker.sdal.tech/beam:latest

The container runs as the non-root user beam (uid 1000), which must be able to write to whatever is mounted at /uploads.

Development

go build ./... && go vet ./... && gofmt -l .

Run locally:

UPLOAD_DIR=/tmp/beam-uploads PORT=8080 DRYRUN_CLEAN=false go run .

Note that without a reverse proxy setting X-Forwarded-Host, generated download URLs fall back to localhost:8080. In production beam runs behind a TLS-terminating ingress that sets X-Forwarded-Proto and X-Forwarded-Host, which is where the URLs come from.

Versioning

build.sh owns the application version via the VERSION variable and passes it to the Docker build as a build argument. Builds append the current git short hash with GIT_SHA so the homepage can display the exact image version near the bottom-left edge.

./build.sh

To push docker.sdal.tech/beam:latest after building:

./build.sh push

Local non-Docker builds display vdev unless a version is injected manually:

go build -ldflags "-X main.version=0.1.0-dev" -o beam .
S
Description
File transfer service via http
Readme MIT
220 KiB
Languages
Go 56.6%
Go Template 40.7%
Shell 1.4%
Dockerfile 1.3%