chore: initialize from backend template 0a6dd0e
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# Fileserver configuration
|
||||
|
||||
Every key below lives under `app.fileserver-platform` (environment form
|
||||
`APP_FILESERVER_PLATFORM_*`). That namespace is the HTTP platform's alone: `app.fileserver.*`
|
||||
belongs to the R2 tabular publication capability and `app.file-export.*` to the R1 CSV export, and
|
||||
the three are deliberately separate so switching one on cannot switch on another.
|
||||
|
||||
While `app.fileserver-platform.enabled` is false none of these keys is bound at all — the
|
||||
auto-configuration that binds them is not processed — so a malformed value in a block nobody
|
||||
enabled cannot fail a startup. Once enabled, binding is strict: an unknown key under the prefix is
|
||||
refused rather than ignored. The defaults are the conservative ones: the
|
||||
capability is off, the admin plane is off, background reclamation is off, and there is no permissive
|
||||
authorization fallback. Turning the capability on is a deliberate act, and so is every surface it
|
||||
exposes.
|
||||
|
||||
## Minimum to start
|
||||
|
||||
```yaml
|
||||
ca-skeleton:
|
||||
fileserver:
|
||||
enabled: true
|
||||
instance-id: ${HOSTNAME} # writer-lease owner; must be unique per node
|
||||
storage:
|
||||
root: /var/lib/backend/files # absolute, outside any webroot or config dir
|
||||
security:
|
||||
access-policy: role-based # or supply your own FileAccessPolicy bean
|
||||
observability:
|
||||
fingerprint-key: ${FILESERVER_FINGERPRINT_KEY}
|
||||
```
|
||||
|
||||
Startup fails, rather than degrading, when any of these is missing or unsafe:
|
||||
|
||||
| Condition | Why it is fatal |
|
||||
| --- | --- |
|
||||
| `security.access-policy` left at `required` with no `FileAccessPolicy` bean | a file capability that authorizes by default is worse than one that refuses to start |
|
||||
| `observability.fingerprint-key` unset while metrics are on | an unkeyed digest of an enumerable identifier is reversible |
|
||||
| the storage root fails a mandatory capability probe | a volume that cannot create atomically, keep staging and content on one FileStore, or refuse symlinks is unsafe, not degraded |
|
||||
| `storage.publish-mode: atomic-move-required` on a volume where the probe could not prove an atomic move | the configured guarantee cannot be delivered |
|
||||
| `security.access-policy: unenforced` under a `prod` profile | a value that was convenient in development must not survive promotion |
|
||||
|
||||
## Authorization — `security`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `access-policy` | `required` | `required` (supply your own bean), `role-based`, or `unenforced` |
|
||||
| `read-roles` | `ROLE_FILE_READ` | grants metadata read and download |
|
||||
| `write-roles` | `ROLE_FILE_WRITE` | grants create, append, finalize, delete, copy, move |
|
||||
| `admin-roles` | `ROLE_FILE_ADMIN` | grants reverify and force-delete, and gates `/internal/fileserver/**` at the servlet chain |
|
||||
|
||||
There is no anonymous-read switch. Every Fileserver route is authenticated by the servlet chain
|
||||
before any application policy is consulted, so such a setting could only ever have described a
|
||||
permission the transport had already refused — a configuration that reads as if it grants access
|
||||
and does not.
|
||||
|
||||
The three tiers do not inherit. An admin role cannot delete through the data plane, and a write role
|
||||
cannot reach the management plane — a role model where "can delete" implied "can force-delete" would
|
||||
make the audited plane reachable through the unaudited one.
|
||||
|
||||
`unenforced` authorizes everything and exists so a developer can exercise upload and download before
|
||||
deciding on a role model. It is refused under a production profile.
|
||||
|
||||
## Storage — `storage`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `root` | `/var/lib/backend/files` | absolute path; the only place a path exists |
|
||||
| `publish-mode` | `atomic-move-preferred` | `atomic-move-required`, `atomic-move-preferred`, `metadata-pointer` |
|
||||
| `buffer-size` | `128KB` | bounds every transfer allocation; memory never scales with file size |
|
||||
| `forbidden-root-ancestors` | `/app,/etc,/usr/share/nginx/html` | roots the storage root must not live under (webroot, config dirs) |
|
||||
|
||||
`root` must be absolute. A relative root resolves against the process working directory, which is
|
||||
one path in a container and another in a test, so it is refused at binding time.
|
||||
|
||||
Three former keys are gone, pinned as constants instead: staging and content share one FileStore,
|
||||
symbolic links are never followed, and the object and its directory are synced before READY. Each
|
||||
is an invariant the atomic publish and the namespace boundary are built on — a deployment that
|
||||
could switch one off would be running a different capability under the same name and the same
|
||||
tests.
|
||||
|
||||
The storage provider has no selector either. There is exactly one implementation, and a `type` key
|
||||
with one legal value is a promise of pluggability that nothing keeps.
|
||||
|
||||
## Upload, download, transfer
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `upload.max-file-size` | `100MB` | hard ceiling; also drives `spring.servlet.multipart.max-file-size` |
|
||||
| `upload.max-request-size` | `110MB` | request envelope; must be at least `max-file-size` |
|
||||
| `upload.initial-reservation` | `8MB` | quota reserved when the length is unknown |
|
||||
| `upload.ttl` | `1h` | how long a resumable upload stays claimable |
|
||||
| `upload.reservation-ttl` | `24h` | how long an unsettled quota reservation survives |
|
||||
| `upload.lease-duration` | `30s` | writer lease; renewed at one third of this |
|
||||
| `upload.max-parts` | `16` | multipart part ceiling |
|
||||
| `upload.require-content-length` | `false` | refuse chunked raw uploads |
|
||||
| `download.cache-control` | `private, no-store` | emitted on every content response |
|
||||
| `download.inline-allowed` | `false` | scriptable content is always an attachment regardless |
|
||||
| `download.max-ranges` | `1` | multi-range responses are opt-in |
|
||||
| `download.max-range-bytes` | `100MB` | total bytes one ranged response may cover |
|
||||
| `download.zero-copy-enabled` | `true` | hand large plaintext responses to the kernel |
|
||||
| `download.zero-copy-minimum-bytes` | `16MB` | below this the syscall setup costs more than it saves |
|
||||
| `transfer.core-size` / `max-size` / `queue-capacity` | `8` / `32` / `64` | blocking transfer pool bounds |
|
||||
| `transfer.await-seconds` | `300` | how long a transfer may occupy a pool thread |
|
||||
|
||||
Zero copy changes no header and no status. When storage declines it — an unreadable region, an
|
||||
unsupported backend — the response is streamed instead and is byte-identical.
|
||||
|
||||
## Verification — `verification`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `timeout` | `5s` | per-verifier ceiling |
|
||||
| `require-media-type-verdict` | `false` | refuse a file whose type could not be determined |
|
||||
| `inline-safe-profile` | `false` | accept scriptable content instead of quarantining it |
|
||||
|
||||
Set `inline-safe-profile: true` only when downloads are never served inline from a trusted origin.
|
||||
|
||||
## Quota and admission — `quota`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `instance-upload-permits` | `16` | concurrent uploads this node admits |
|
||||
| `scope-upload-permits` | `4` | concurrent uploads one namespace admits |
|
||||
| `direct-download-permits` | `64` | concurrent non-delegated downloads |
|
||||
| `soft-high-water` | `0.70` | storage fraction at which pressure is reported |
|
||||
| `hard-high-water` | `0.85` | storage fraction at which uploads are refused |
|
||||
|
||||
When the storage fraction cannot be read, admission treats it as unknown and does not apply the
|
||||
high-water rule — a synthetic `0` would silently disable the guard, and a synthetic `1` would take
|
||||
the capability down over a failed syscall.
|
||||
|
||||
## Background reclamation — `cleanup`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enabled` | `false` | run the cleanup worker on this node |
|
||||
| `interval` | `60s` | fixed delay between batches, not fixed rate |
|
||||
| `max-items` | `100` | items one batch may claim |
|
||||
| `max-bytes` | `1GB` | bytes one batch may reclaim |
|
||||
| `retry-backoff` | `5m` | how long a failed item waits before it is due again |
|
||||
|
||||
The worker deletes physical objects, so it is off until a deployment decides otherwise. A node
|
||||
without it still queues cleanup items; another node or an operator reclaims them. An item that fails
|
||||
eight times is abandoned rather than retried forever — it stays visible to an operator, parked
|
||||
rather than discarded.
|
||||
|
||||
## Management plane — `admin`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enabled` | `false` | expose `/internal/fileserver/**` |
|
||||
| `orphan-minimum-age` | `1h` | how long an unreferenced object must exist before a scan may name it |
|
||||
|
||||
Publishing content and committing its record are two steps. Anything younger than
|
||||
`orphan-minimum-age` is assumed to be mid-commit rather than abandoned; shortening this makes
|
||||
concurrent uploads look like orphans.
|
||||
|
||||
## Front-proxy delegation — `nginx`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enabled` | `false` | emit `X-Accel-Redirect` instead of a body |
|
||||
| `internal-prefix` | `/__files/` | must be an `internal` location resolving to the content root |
|
||||
| `object-suffix` | `.bin` | layout suffix the proxy appends |
|
||||
| `minimum-size` | `16MB` | below this the application serves the transfer itself |
|
||||
|
||||
Delegation is decided only after authorization and the READY gate, so an internal redirect can only
|
||||
ever name content the caller was already allowed to read.
|
||||
|
||||
## Protocols — `tus`
|
||||
|
||||
| Key | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enabled` | `false` | expose the tus 1.0 endpoints |
|
||||
|
||||
The HTTPbis resumable-upload draft-12 surface is experimental and documented in
|
||||
[support-matrix.md](support-matrix.md).
|
||||
Reference in New Issue
Block a user