No description
  • JavaScript 93.3%
  • CSS 6.7%
Find a file
2026-06-16 15:19:37 +03:00
deploy v1 2026-06-16 14:16:02 +03:00
src fix: prewiev Bot expires links 2026-06-16 15:19:37 +03:00
.env.example v1 2026-06-16 14:16:02 +03:00
.gitignore v1 2026-06-16 14:16:02 +03:00
package-lock.json v1 2026-06-16 14:16:02 +03:00
package.json v1 2026-06-16 14:16:02 +03:00
README.md v1 2026-06-16 14:16:02 +03:00
schema.sql v1 2026-06-16 14:16:02 +03:00

One-Time File Share

A small self-hosted file-sharing app. You upload a file, get a one-time-use download link, hand it to a friend — after one successful download (or once it expires) the link is dead. Files persist in your account until you delete them, so you can mint a fresh link to re-share.

Built to run on a headless Fedora box behind nginx at the sub-path /fileShare, alongside other apps.

Features

  • One-time download links, reserved atomically so exactly one download succeeds (race-safe).
  • Time-based expiry on top of one-time use (LINK_TTL_HOURS).
  • Accounts with email verification (SMTP); unverified users can't upload.
  • Per-user dashboard: see each file's scan status, link status, expiry, copy the URL, mint a new link, or delete.
  • Multi-file uploads are bundled into a single .zip behind one link.
  • Antivirus scan on every upload via ClamAV (clamd); infected files are rejected and never get a link.
  • Lazy background compression (gzip) to save disk; transparently decompressed on download.
  • Disk-space monitor that refuses uploads when free space is low.
  • Admin dashboard to manage all users, files and links.
  • Whole site is noindex / unlisted.

Requirements

  • Node.js ≥ 20
  • MySQL (existing server is fine)
  • ClamAV clamd listening on TCP (optional in dev — set DISABLE_AV=1)
  • An SMTP account for verification emails (optional in dev — set DISABLE_EMAIL_VERIFICATION=1)

Setup

  1. Database — create the schema yourself (no auto-migration):
    mysql -u <user> -p <dbname> < schema.sql
    
  2. Install deps:
    npm install
    
  3. Configure:
    cp .env.example .env
    # set SESSION_SECRET, DB_*, ADMIN_EMAIL, PUBLIC_BASE_URL, SMTP_*, CLAMD_*
    node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"  # for SESSION_SECRET
    
  4. Run:
    npm start
    
    The app listens on 127.0.0.1:$PORT and mounts at $BASE_PATH (e.g. http://127.0.0.1:8077/fileShare).

Local development (DB over SSH tunnel)

Tunnel the server's MySQL to your laptop, then point .env at the tunnel:

ssh -L 3307:127.0.0.1:3306 user@server
DB_HOST=127.0.0.1
DB_PORT=3307
DISABLE_AV=1                     # if you don't run clamd locally
DISABLE_EMAIL_VERIFICATION=1     # auto-verifies new accounts; logs verify links
PUBLIC_BASE_URL=http://127.0.0.1:8077/fileShare
TRUST_PROXY=0

Production (nginx + systemd)

  • Reverse proxy: see deploy/nginx-fileShare.conf. The app keeps the /fileShare prefix, so proxy_pass has no trailing slash. Raise client_max_body_size to match MAX_UPLOAD_BYTES.
  • Service: see deploy/fileshare.service. Set PUBLIC_BASE_URL=https://your.domain/fileShare and TRUST_PROXY=1.
  • The first account whose email equals ADMIN_EMAIL is auto-promoted to admin on startup.

How it behaves (worth knowing)

  • Aborted downloads still burn the link. The token is reserved the moment a valid download starts, which is the safe reading of "one-time use." If a transfer fails, mint a new link from the dashboard.
  • Infected uploads are deleted immediately and never stored or linked. If clamd is unreachable, AV_FAIL_MODE=block (default) rejects the upload; skip accepts it and marks the scan skipped.
  • Compression is lazy: the upload returns instantly and a background worker gzips the file shortly after (skipping already-compressed types in COMPRESS_SKIP_EXTS). Downloads work whether or not compression has finished.

Key env vars

Var Purpose
BASE_PATH Sub-path mount (e.g. /fileShare)
PUBLIC_BASE_URL Absolute URL used to build copyable links
LINK_TTL_HOURS Lifetime of a freshly minted link
MIN_FREE_BYTES Uploads refused below this free space
MAX_UPLOAD_BYTES Per-file cap
ADMIN_EMAIL Account promoted to admin on startup
AV_FAIL_MODE block or skip when clamd is down
DISABLE_AV / DISABLE_EMAIL_VERIFICATION Dev toggles

Project layout

src/
  server.js          app wiring, session, mounting, workers
  config.js          env parsing
  db.js              mysql2 pool
  auth.js            sessions, register/login/logout, guards, CSRF check
  mailer.js          SMTP verification emails
  links.js           link minting + per-file link state
  storage.js         on-disk path helpers
  log.js             file + stdout logger / audit trail
  routes/            pages, upload, download, files, admin, verify
  services/          disk, antivirus, bundle (zip), compression (gzip)
  views.js           server-rendered HTML
  public/style.css
schema.sql           run this against MySQL
deploy/              nginx + systemd samples