169 lines
3.7 KiB
Markdown
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)
|
|
```
|