# Nanodrop A minimal self-hosted file sharing platform. Upload a file, get a shareable link. No frills. - Server-rendered HTML, no client-side framework - SQLite database, files stored on disk - JWT authentication via httpOnly cookies - REST API alongside the web UI ## Stack - **Runtime:** Node.js 22 - **Framework:** Fastify - **Database:** SQLite (better-sqlite3) - **Language:** TypeScript (ESM, run directly via tsx) --- ## Quick start (local) ```sh npm install JWT_SECRET=changeme npm run dev ``` Create a user first: ```sh npm run register-user -- --username alice --password secret ``` Then open `http://localhost:3000`. --- ## Docker ### Build and run ```sh docker compose up -d ``` Requires a `.env` file (or environment variables) with at minimum: ```env JWT_SECRET=your-secret-here ``` All data (database + uploads) is stored in the `nanodrop-data` Docker volume. ### Register a user in Docker ```sh docker compose run --rm register-user --username alice --password secret ``` ### Environment variables | Variable | Default | Description | |---|---|---| | `JWT_SECRET` | *(required)* | Secret key for signing JWTs | | `JWT_EXPIRY` | `7d` | JWT token lifetime | | `PORT` | `3000` | Port to listen on | | `HOST` | `0.0.0.0` | Host to bind | | `BASE_URL` | `http://localhost:3000` | Public base URL (used in share links) | | `DB_PATH` | `./data/nanodrop.db` | SQLite database path | | `UPLOAD_DIR` | `./data/uploads` | Upload storage directory | | `LOG_FILE` | `./data/nanodrop.log` | Auth and access log path | | `MAX_FILE_SIZE` | `104857600` | Max upload size in bytes (100 MB) | | `COOKIE_SECURE` | `false` | Set `true` when serving over HTTPS | | `TRUST_PROXY` | `false` | Set `true` when behind a reverse proxy | ### Reverse proxy (nginx example) When running behind nginx, set `TRUST_PROXY=true` so Nanodrop sees the real client IP in logs. ```nginx server { server_name files.example.com; location / { proxy_pass http://127.0.0.1:3000; 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; client_max_body_size 110M; } } ``` --- ## Fail2ban Nanodrop logs failed login attempts to `LOG_FILE` (default `./data/nanodrop.log`) in this format: ``` [2026-03-03T12:00:00.000Z] AUTH_FAILURE ip=203.0.113.42 user-agent="curl/8.0" username="admin" ``` ### 1. Create a filter `/etc/fail2ban/filter.d/nanodrop.conf`: ```ini [Definition] failregex = ^.* AUTH_FAILURE ip= .*$ ignoreregex = ``` ### 2. Create a jail `/etc/fail2ban/jail.d/nanodrop.conf`: ```ini [nanodrop] enabled = true filter = nanodrop logpath = /path/to/nanodrop.log maxretry = 5 findtime = 60 bantime = 600 ``` Adjust `logpath` to wherever your `LOG_FILE` is. With Docker, the log file lives inside the `nanodrop-data` volume — mount it to a host path or bind-mount a host directory instead of the named volume to make it accessible to fail2ban: ```yaml # docker-compose.yml override volumes: - /var/lib/nanodrop:/app/data ``` Then set `logpath = /var/lib/nanodrop/nanodrop.log`. ### 3. Reload fail2ban ```sh sudo fail2ban-client reload sudo fail2ban-client status nanodrop ``` --- ## Development ```sh npm run dev # start with hot reload via tsx npm test # run all tests (vitest) npm run build # type check (tsc --noEmit) ```