Sign inSign up

theinfobots/tmpshare

By theinfobots

•Updated 4 months ago

Self-hosted temporary file sharing with auto-expiry by time and/or download count.

Image
Networking
0

169

theinfobots/tmpshare repository overview

⁠tmpshare

Self-hosted temporary file sharing. Upload a file, get a link, the link dies on a timer or after N downloads. No accounts, no tracking, no S3.

Built with FastAPI + SQLite. ~160 MB image, runs as non-root, single container.

Docker Hub: theinfobots/tmpshare⁠


⁠Features

  • Upload via web UI or curl (multipart)
  • Expiry by time (e.g. 1 h, 24 h, up to 7 days) and/or max downloads
  • Auto-purge of expired/exhausted files (background sweep + on-access check)
  • One-shot delete link returned at upload
  • Streaming uploads with size cap (default 1 GiB)
  • SQLite metadata + flat-file blob storage on a single volume
  • /healthz endpoint for Docker healthchecks
  • Runs behind a reverse proxy (honors X-Forwarded-*)

⁠Quick start

docker run -d --name tmpshare \
  -p 8000:8000 \
  -v tmpshare_data:/data \
  --restart unless-stopped \
  theinfobots/tmpshare:latest

Open http://localhost:8000⁠.

⁠docker-compose
services:
  tmpshare:
    image: theinfobots/tmpshare:latest
    container_name: tmpshare
    restart: unless-stopped
    ports:
      - "8000:8000"
    environment:
      PUBLIC_BASE_URL: https://files.example.com
      MAX_FILE_SIZE: 1073741824        # 1 GiB
      DEFAULT_EXPIRY_SECONDS: 86400    # 24 h
      MAX_EXPIRY_SECONDS: 604800       # 7 d
      CLEANUP_INTERVAL_SECONDS: 300
    volumes:
      - tmpshare_data:/data

volumes:
  tmpshare_data:

⁠Configuration

All settings via environment variables:

VariableDefaultDescription
DATA_DIR/dataDirectory for files.db + blobs/
MAX_FILE_SIZE1073741824Hard limit per upload, in bytes (1 GiB)
DEFAULT_EXPIRY_SECONDS86400Default TTL when client omits one
MAX_EXPIRY_SECONDS604800Upper bound on requested TTL
CLEANUP_INTERVAL_SECONDS300Background purge interval
PUBLIC_BASE_URL(empty)If set, returned links use this base (e.g. https://files.example.com)

Persist /data to keep uploads across container restarts.

⁠Using it

⁠Web UI

Go to /, pick a file, choose expiry hours and max downloads (0 = unlimited until expiry), submit. You get a download link and a delete link.

⁠Upload via curl
curl -F "file=@./report.pdf" \
     -F "expires_in=3600" \
     -F "max_downloads=1" \
     https://files.example.com/api/upload

Response:

{
  "id": "ohyZeK0h0g08Y94W",
  "filename": "report.pdf",
  "size": 482910,
  "expires_at": 1778680778,
  "max_downloads": 1,
  "download_url": "https://files.example.com/d/ohyZeK0h0g08Y94W",
  "delete_url":   "https://files.example.com/api/delete/ohyZeK0h0g08Y94W?token=..."
}
⁠Download
curl -OJ https://files.example.com/d/ohyZeK0h0g08Y94W

Once max_downloads is reached the blob is purged within ~1 second; after expires_at the next request returns 410 Gone and triggers purge.

⁠Inspect
curl https://files.example.com/api/info/<id>
⁠Delete early

Use the delete_url returned at upload, or:

curl -X DELETE "https://files.example.com/api/delete/<id>?token=<delete_token>"

⁠API summary

MethodPathPurpose
GET/Web UI
POST/api/uploadMultipart upload (file, expires_in, max_downloads)
GET/d/{id}Download (counts toward max_downloads)
GET/api/info/{id}Metadata (no body)
DELETE/api/delete/{id}?token=...Purge with delete token
GET/healthzLiveness check

⁠Operations

# Tail logs
docker logs -f tmpshare

# Restart
docker restart tmpshare

# Update to latest image
docker pull theinfobots/tmpshare:latest
docker rm -f tmpshare
docker run -d --name tmpshare -p 8000:8000 \
  -v tmpshare_data:/data --restart unless-stopped \
  theinfobots/tmpshare:latest

# Backup data (DB + blobs)
docker run --rm -v tmpshare_data:/data -v "$PWD":/backup alpine \
  tar czf /backup/tmpshare-$(date +%F).tgz -C / data

# Wipe everything
docker rm -f tmpshare && docker volume rm tmpshare_data

⁠Reverse proxy

Nginx example:

server {
  listen 443 ssl http2;
  server_name files.example.com;

  client_max_body_size 1100m;   # match MAX_FILE_SIZE

  location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_request_buffering off;
  }
}

Set PUBLIC_BASE_URL=https://files.example.com so generated download links use the right domain.

⁠Notes & limits

  • IDs use 96 bits of entropy (secrets.token_urlsafe(12)) — unguessable but not authenticated. Treat the URL as the credential. Anyone with the link can download until expiry.
  • No virus scanning, no content moderation, no rate limiting. Don't expose this open to the internet without a reverse proxy and access controls if you don't want strangers using your storage.
  • SQLite is fine for thousands of files; this isn't built for high-concurrency multi-node.
  • Files larger than MAX_FILE_SIZE are rejected mid-stream with 413.

⁠Tags

  • latest — current build
  • YYYY-MM-DD — dated snapshot

⁠License

MIT.

Tag summary

Content type

Image

Digest

sha256:8e3391ab1…

Size

52.9 MB

Last updated

4 months ago

docker pull theinfobots/tmpshare