Private file drop with short links, TTL, and token-based authorization.
upload via curl → short link → wget just works
# 1. Build
make build
# or: go build -o miniupload ./cmd/miniupload/
# 2. Configure
cp .env.example .env
# edit .env — set UPLOAD_TOKEN and BASE_URL
# 3. Run
source .env && ./miniuploadThe server starts on http://localhost:3000 (or whatever PORT is set to in .env).
| Variable | Description | Default |
|---|---|---|
BASE_URL |
Public address used in generated links | http://localhost:PORT |
UPLOAD_TOKEN |
Bearer token for upload authorization | (empty = no protection) |
PORT |
HTTP port | 3000 |
DATA_DIR |
Directory for files and database | ./data |
DATABASE_PATH |
Path to SQLite database | ./data/uploads.sqlite |
MAX_FILE_SIZE_MB |
Maximum file size in MB | 1024 |
DEFAULT_TTL |
TTL when not specified by the client | 24h |
CLEANUP_INTERVAL_SECONDS |
How often to sweep expired files | 300 |
curl -X POST https://your-domain.com/upload \
-H "Authorization: Bearer TOKEN" \
-F "file=@archive.zip" \
-F "ttl=7d"Response:
{
"url": "https://your-domain.com/aB92kLm3xY",
"id": "aB92kLm3xY",
"expires_at": "2026-06-16T12:00:00Z"
}wget https://your-domain.com/aB92kLm3xYThe file is saved under its original name via Content-Disposition.
export UPLOAD_URL=https://your-domain.com
export UPLOAD_TOKEN=your-token
./upload.sh archive.zip 7d
# → https://your-domain.com/aB92kLm3xY1h · 6h · 24h · 3d · 7d · 30d
Open https://your-domain.com/ in a browser.
- Token entered once is saved in localStorage
- Drag & drop or click to select a file
- Upload progress bar
- Generated link with a copy button and expiry date
make build # compile with version from git describe
make test # go test -race ./...
make vet # go vet
make run # build + run
make clean # remove binary
make build-all # cross-compile all platforms to dist/make build
scp miniupload user@server:/opt/miniupload/sudo useradd -r -s /sbin/nologin -d /opt/miniupload miniupload
sudo mkdir -p /opt/miniupload/data/files
sudo chown -R miniupload:miniupload /opt/miniupload
sudo cp .env.example /opt/miniupload/.env
sudo nano /opt/miniupload/.env
sudo chmod 600 /opt/miniupload/.env
sudo chown miniupload:miniupload /opt/miniupload/.envsudo cp deploy/miniupload.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now miniupload
sudo systemctl status miniuploadsudo cp deploy/miniupload.openrc /etc/init.d/miniupload
sudo chmod +x /etc/init.d/miniupload
sudo rc-update add miniupload default
sudo rc-service miniupload startminiupload/
├── cmd/miniupload/
│ ├── main.go # entry point, routing
│ ├── config.go # config struct, env loading, TTL parsing
│ ├── db.go # SQLite, schema, ID generation
│ ├── handlers.go # HTTP handlers
│ ├── cleanup.go # background cleanup goroutine
│ ├── fileutil.go # filename sanitization, MIME detection
│ ├── handlers_test.go # integration tests
│ └── index.html # embedded frontend
├── deploy/
│ ├── miniupload.service # systemd
│ └── miniupload.openrc # OpenRC
├── .github/workflows/
│ ├── ci.yml # test on every push/PR
│ └── release.yml # build + release on v* tag
├── Makefile
├── upload.sh # CLI helper
├── .env.example
└── go.mod / go.sum
- Files on disk use random names — the original filename never touches the filesystem path
- Path traversal blocked at two levels: sanitization +
filepath.Abscheck - Files are served as
attachment— never executed by the server - Token does not appear in logs
- Upload without token → 401
- Expired files → 410 Gone
- Body size capped via
MaxBytesReaderbefore multipart parsing