Files
nanodrop/README.md
2026-03-03 16:20:47 -08:00

169 lines
3.7 KiB
Markdown

# 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
Set `TRUST_PROXY=true` when running behind a reverse proxy so Nanodrop sees the real client IP in logs.
**Caddy** (`Caddyfile`):
```caddy
files.example.com {
reverse_proxy localhost:3000
request_body {
max_size 110MB
}
}
```
Caddy sets `X-Forwarded-For` and handles TLS automatically. Set `COOKIE_SECURE=true` since traffic to the app arrives over HTTPS.
**nginx** (`sites-available/nanodrop`):
```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=<HOST> .*$
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)
```