Rust file-hosting CDN API
  • TypeScript 42.8%
  • Rust 34.7%
  • CSS 19.1%
  • Dockerfile 1.3%
  • HTML 1.2%
  • Other 0.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
R3V3RS3_P4RAD0X ee3d4bf665
All checks were successful
CI / frontend (push) Successful in 17s
CI / backend (push) Successful in 4m32s
Document project layout and correct install env hint to ADMIN_PASSWORD.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 21:15:25 +01:00
.forgejo Document project layout and correct install env hint to ADMIN_PASSWORD. 2026-08-09 21:15:25 +01:00
deploy Document project layout and correct install env hint to ADMIN_PASSWORD. 2026-08-09 21:15:25 +01:00
frontend Document project layout and correct install env hint to ADMIN_PASSWORD. 2026-08-09 21:15:25 +01:00
migrations Document project layout and correct install env hint to ADMIN_PASSWORD. 2026-08-09 21:15:25 +01:00
src Document project layout and correct install env hint to ADMIN_PASSWORD. 2026-08-09 21:15:25 +01:00
.dockerignore Add Docker, Compose, and systemd deployment packaging. 2026-07-25 12:23:48 +01:00
.env.example Replace API-key auth with user accounts, public browsing, and admin approval. 2026-07-25 12:34:29 +01:00
.gitignore Add React admin UI with file list API and SPA serving. 2026-07-25 12:18:21 +01:00
Cargo.lock Replace API-key auth with user accounts, public browsing, and admin approval. 2026-07-25 12:34:29 +01:00
Cargo.toml Replace API-key auth with user accounts, public browsing, and admin approval. 2026-07-25 12:34:29 +01:00
compose.yml Replace API-key auth with user accounts, public browsing, and admin approval. 2026-07-25 12:34:29 +01:00
Dockerfile Add Docker, Compose, and systemd deployment packaging. 2026-07-25 12:23:48 +01:00
README.md Document project layout and correct install env hint to ADMIN_PASSWORD. 2026-08-09 21:15:25 +01:00

CDN

Rust file-hosting API (Axum + SQLite) with a React SPA. Anyone can browse and download; uploading and deleting require a logged-in approved account. Admins approve registrations and manage users.

🎯 Purpose

A small self-hosted CDN / file locker: store blobs on disk, metadata in SQLite, and serve them over short shareable IDs. One process can run the API and the built frontend.

🚀 Quick start

From the repo root:

cp .env.example .env
# set ADMIN_PASSWORD (required, min 8 characters)

cargo run

In a second terminal, for hot-reload UI:

cd frontend
npm install
npm run dev
Surface URL
API (+ production SPA if built) http://127.0.0.1:8080
Vite UI (dev) http://127.0.0.1:5173

Vite proxies /api, /health, and short file IDs to the API. On first boot, if no approved admin exists, the server seeds one from ADMIN_USERNAME / ADMIN_PASSWORD.

Production-style (single process)

cd frontend && npm install && npm run build
cd .. && cargo run

When STATIC_DIR (default ./frontend/dist) contains index.html, Axum serves the SPA alongside the API.

📁 Layout

cdn/
├── src/                 # Rust API binary
├── frontend/            # React + Vite SPA
├── migrations/          # SQLite schema (sqlx migrate)
├── deploy/              # systemd, Caddy, install helper
├── .forgejo/workflows/  # CI
├── compose.yml          # Docker Compose
├── Dockerfile
├── .env.example
└── data/                # runtime (gitignored): cdn.db + files/

🏗️ Architecture

flowchart LR
  Client[Browser / curl]
  Axum[Axum process]
  SPA[frontend/dist]
  DB[(SQLite cdn.db)]
  Disk[data/files]

  Client --> Axum
  Axum --> SPA
  Axum --> DB
  Axum --> Disk
  • API — Axum routes under /api/*, public downloads at /{id}, liveness at /health.
  • Auth — HTTP-only cdn_session cookie; Argon2 password hashes; sessions in SQLite.
  • Storage — file bytes named by ID under {DATA_DIR}/files/; metadata in files table.
  • SPA — optional static overlay; without a build, only the API starts (with a warning log).

💡 Auth model

Who Can do
Public List, view, download, copy share links
Logged-in approved user Upload, delete any file
Admin Manage accounts (create / approve / reject / delete)

Self-registration creates a pending user until an admin approves. Pending and rejected accounts cannot log in.

🛠️ API

Method Path Auth Description
GET /health no Liveness ({"status":"ok"})
GET /api/files no List files (q, type)
GET /{id} no Download / inline view
POST /api/auth/register no Request account (pending)
POST /api/auth/login no Login (sets cdn_session)
POST /api/auth/logout session Logout
GET /api/auth/me session Current user
POST /api/upload session Multipart field file
DELETE /api/files/{id} session Delete file
GET /api/admin/users admin List users
POST /api/admin/users admin Create approved user
POST /api/admin/users/{id}/approve admin Approve pending
POST /api/admin/users/{id}/reject admin Reject pending
DELETE /api/admin/users/{id} admin Delete user

GET /api/files query params:

  • q — case-insensitive substring match on original name or author username
  • typeall (default), image, video, audio, document, other

Upload IDs are 10-character nanoids. Download path IDs must be 821 chars of [A-Za-z0-9_-].

Examples

# public list
curl -sS "http://127.0.0.1:8080/api/files"

# login (stores cdn_session cookie)
curl -sS -c cookies.txt -X POST http://127.0.0.1:8080/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"your-password"}'

# upload
curl -sS -b cookies.txt -X POST http://127.0.0.1:8080/api/upload \
  -F "file=@./photo.jpg"

⚙️ Configuration

Copy .env.example. Important variables:

Variable Default Notes
BIND 0.0.0.0:8080 Listen address
ADMIN_USERNAME admin Seeded only if no approved admin exists
ADMIN_PASSWORD (required) Min 8 chars
DATA_DIR ./data SQLite + files/ blobs
DATABASE_URL sqlite:{DATA_DIR}/cdn.db?mode=rwc Optional override
MAX_UPLOAD_BYTES 52428800 (50 MiB) Enforced on upload
PUBLIC_BASE_URL unset Absolute URLs in list/upload responses
STATIC_DIR ./frontend/dist SPA root when index.html exists
COOKIE_SECURE auto from HTTPS PUBLIC_BASE_URL Session cookie Secure flag
SESSION_DAYS 30 Session lifetime
CORS_ORIGIN * Exact origin + credentials when not *
CDN_PORT 8080 Host port for Compose only
RUST_LOG info Tracing filter

📦 Dependencies

  • Rust toolchain (edition 2024) — cargo run / cargo build --release
  • Node.js 22+ recommended — frontend build and Vite
  • SQLite via sqlx (embedded; no separate DB server)
  • Optional: Docker / Compose, Caddy, systemd (see deploy/)

🚀 Deployment

Docker Compose

cp .env.example .env
# set ADMIN_PASSWORD; set PUBLIC_BASE_URL=https://your.domain in production

docker compose up -d --build

Compose mounts volume cdn-data at /data and sets DATA_DIR / STATIC_DIR for the image.

Bare metal / systemd

See deploy/README.md (install-binary.sh, cdn.service, Caddyfile.example).

⚠️ Pitfalls

  • ADMIN_PASSWORD is required — the process exits on startup without it.
  • Admin seed is one-shot — changing ADMIN_PASSWORD later does not update an existing admin; use the DB or create another admin via the API.
  • Any approved user can delete any file — delete is not owner-scoped.
  • CORS * and cookies — wildcard CORS does not send credentials; use Vites proxy (same-origin) or set an exact CORS_ORIGIN with credentials.
  • Share links — without PUBLIC_BASE_URL, API returns relative URLs (/{id}); set the public origin behind a reverse proxy.
  • Reverse proxy body size — raise proxy limits to match MAX_UPLOAD_BYTES (see Caddy example).