runtime: ranges, health, logs, supervised loops, strict config

Media answers Range requests with 206 and Content-Range, so videos seek
and Safari plays them. Sealed files too: the vault's opener now decrypts
by record position (STREAM BE32 through StreamPrimitive, the same
construction and the same files), so a range starts at the record that
holds its first byte. Compression is limited to text, because a gzipped
body no longer matches the byte ranges a player asks for.

GET /api/health answers without a session once the database does, and
the compose healthcheck asks it instead of only opening the port.
Requests are logged at info with share tokens and keys left out of the
path and no query strings. A request that is not answered in 30 seconds
gets a 503, except uploads, imports, the export, re-sealing and backups.

The backup, mail, sweep and SFTP loops run under a supervisor that logs
a panic and starts the loop again. The backup loop starts without a
Proton session and picks up a later proton-login without a restart. A
lock file keeps backup-now and the server from mirroring at once, and an
unreadable session key file turns backups off instead of failing boot.

Numbers in the configuration are checked against bounds at startup, so
a session length or an upload limit can no longer overflow later, and a
switch that is neither on nor off is an error rather than off. Config
and MailConfig print with the password, secret and SMTP URL redacted.

A session epoch is signed into each cookie (migration 0014). :logout!
advances it, which ends every session, and a changed WORKSPACE_PASSWORD
advances it on the next start. Cookies issued before this keep working
in the first epoch.
This commit is contained in:
nvrl committed 2026-10-20 02:01:50 +02:00
1 parent d1f27ac18d
commit ee13ae44fa
20 files changed
+771 -172

No files matched your search

+21 -4
View File
@@ -37,7 +37,11 @@ spelling still accepted behind it, so a server configured before the rename
keeps running untouched.
`WORKSPACE_SECRET` signs the session cookie; generate one with
`openssl rand -base64 32`. Changing it logs every device out. The database and
`openssl rand -base64 32`. Changing it logs every device out, and so does
changing `WORKSPACE_PASSWORD` or `:logout!`, which ends every session but
needs no restart. A setting that is set but cannot be read — a number out of
range, a switch that is neither on nor off — stops the start with the reason,
rather than being taken for its default. The database and
uploads live under `WORKSPACE_DATA_DIR` (`./data` by default) — that directory is
the entire backup, and it is exactly what the Proton Drive mirror copies.
@@ -445,11 +449,14 @@ both halves.
## API
Everything except the two share routes requires the session cookie.
Everything except the login, the health check and the two share routes
requires the session cookie. Media answers `Range` requests, so a video seeks,
sealed or not.
| method | path | purpose |
| --- | --- | --- |
| `POST` | `/api/login`, `/api/logout` | session |
| `GET` | `/api/health` | public: up, and the database answers — for a supervisor |
| `POST` | `/api/login`, `/api/logout` | session; `/api/logout?everywhere=true` ends every one |
| `GET` | `/api/me` | current user |
| `GET`/`POST` | `/api/spaces` | the spaces / create one |
| `GET`/`POST` | `/api/nodes` | children (`?parent=`) or search (`?q=`, `?space=`) / create |
@@ -494,7 +501,17 @@ so a proxy must pass the `Host` header through unchanged (traefik and nginx's
`proxy_set_header Host $host` both do). Only the upload routes accept a body as
large as `WORKSPACE_MAX_UPLOAD_MB`; everything else stops at 16 MiB, and the
login at 16 KiB, before anyone has proved who they are. `SIGTERM` gets in-flight
requests eight seconds to finish, inside the ten `docker stop` waits.
requests eight seconds to finish, inside the ten `docker stop` waits. Any
other request that has not been answered after 30 seconds is given up on with
a `503`, except the ones that move files around: uploads, imports, the export,
re-sealing and a backup run.
Every request is logged at `info`, path and status and time, never a query
string, and with a share link's token and key left out. A background loop that
panics — the mirror, the mail, the sweep, the SFTP server — is logged and
started again rather than lost until the next restart. If
`WORKSPACE_SESSION_KEY_FILE` cannot be read the server starts anyway with
backups off, and `:backup` says why.
### Docker