Skip to content

Server behaviour, errors, and health

Quota enforcement, the fixed Go error mapping, the server configuration and shutdown sequence, and the health endpoint.

Quota and error semantics

Enforcement is at the filesystem: the persistent volume claim (PVC) size equals the quota, so the write path sees ENOSPC/EDQUOT. In addition, PUT pre-checks free space with statfs and rejects with 507 Not enough disk space. when the request cannot fit. The pre-check is skipped when the body length is unknown, and a failed statfs call answers 500 rather than guessing. Mid-write quota exhaustion returns 507 with a message naming disk quota.

The Go error mapping (api/common.go) is fixed:

Condition HTTP Meaning
ENOSPC, EDQUOT 507 A write finds the volume full (ENOSPC), or exceeds the site storage quota (EDQUOT). The PVC size equals the quota, so quota exhaustion arrives as one of these errors.
EEXIST 409 The file or directory that the request creates already exists.
EACCES, EPERM 403 Filesystem permissions deny the operation to the user account of the API process. This is not a decision of the Site API access rules.
ENOENT 404 The path does not exist, or a parent directory of the path does not exist.
EINVAL 400 A system call received arguments that it cannot accept, for example a name that the filesystem rejects.
EIO 502 The storage device failed during a read or a write. The fault is in the volume or its backing storage, not in the request.
ECONNREFUSED 503 The target address refused the connection. No service listens at that address and port.
ECONNRESET 504 The peer closed an open connection while the operation was in progress.
ETIMEDOUT 408 The peer did not answer before the time limit expired.
anything else 500 Every failure that the mapping above does not classify. The message field carries the text of the error.

Each file operation uses an os.Root opened on the site root (api/site_root.go). The kernel resolves every path component under the root, and os.Root rejects absolute symlinks and symlinks that leave the root. A request for a path that escapes the root fails in the parse middleware with 400, before any file operation (api/parse_middleware.go).

Remember the 404 ambiguity on public endpoints: it means "missing or .htpasswd-hidden" (10-security). Authentication errors (401/403) come from the Lua layer, not from Go.

DELETE supports match bulk deletes: 200 when all entries were removed, 207 with a per-entry report when mixed, 500 when all failed, and 404 when no entry matched. Deleting the volume root answers 400, and each bulk entry is re-checked against the root. Deletions above 500 entries or 100 MB (strictly more; environment thresholds ASYNC_DELETE_THRESHOLD_ITEMS and ASYNC_DELETE_THRESHOLD_BYTES) respond 202 and continue in the background; a plain directory delete estimates its size with a bounded probe walk and takes the same path. Background failures are logged, not reported.

Server and shutdown

Configuration comes from the environment (besides the upload and delete tuning above): MASTER_TOKEN and SITE_PATH (both required at startup), SITE_TYPE (default private), PORT, DEBUG, and IMAGE_TAG. Content paths are fixed (/www/ volume, /www/html root, /www/database/tokens.db).

The engine logs one line per request (method, path, status, size, latency, client, user agent) to stdout, recovers panics as 500, trusts only the loopback proxy for client-address headers, and bounds the request line and headers at 1 MiB with a 30-second header-read and 120-second idle timeout. There is no request body cap on PUT or JSON bodies, and no rate limiting: upload backpressure is the only flow control.

On SIGTERM/SIGINT the service drains HTTP requests for 15 seconds, waits for in-flight background deletes, stops the history cleanup job, drains the history write workers, and closes SQLite (api/main.go). Keep the pod termination grace above this drain when changing it.

Health endpoint

GET /api/v2/health/ returns 200, or 503 when storage is at or above 98% used, 100% full, or the local proxy health probe (GET http://127.0.0.1:8080/<space>/<site>/s/healthz/) fails. Messages report usage thresholds at 80/90/95/98/100%. The probe passes through the proxy's stub_status page, and its body is echoed in the health response's nginx.response; the response is always Cache-Control: no-cache, no-store. The health location always allows the pod loopback address, so the probe works whatever trusted_ips holds (see 10-security).