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).