feat(files): 文件管理可新建、建目录、删除、重命名和上传,写入内容拆成多个环境变量不再超内核单变量上限
This commit is contained in:
37 files changed
+5972
-416
No files matched your search
+323
-1
@@ -1202,6 +1202,38 @@ paths:
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
|
||||
/api/v1/internal/file-uploads/{id}:
|
||||
get:
|
||||
tags: [files]
|
||||
operationId: internalFileUpload
|
||||
summary: Stream one staged file upload to the Job landing it (one-time bearer token).
|
||||
description: >-
|
||||
PUT /api/v1/servers/{name}/files/upload stages the body on felis-api's disk
|
||||
and creates a Job to land it in the world volume; the Job fetches the bytes
|
||||
here. The Job holds no service token, so the route is public on the internal
|
||||
face and the bearer token minted with the upload is the whole check. The
|
||||
token opens its upload once. An unknown id, a wrong or missing token and a
|
||||
spent token are all the same 404, so the route says nothing about which
|
||||
uploads exist.
|
||||
x-felis-face: [internal]
|
||||
x-felis-tier: public
|
||||
security: []
|
||||
parameters:
|
||||
- { name: id, in: path, required: true, schema: { type: string } }
|
||||
- name: Authorization
|
||||
in: header
|
||||
required: true
|
||||
description: Bearer followed by the token minted with the upload.
|
||||
schema: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: The staged bytes, verbatim, with their Content-Length.
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema: { type: string, format: binary }
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
/api/v1/internal/servers/{name}/join-event:
|
||||
post:
|
||||
tags: [servers-internal]
|
||||
@@ -4224,6 +4256,12 @@ paths:
|
||||
The sha256 a read returned. When present, the write is refused with
|
||||
409 file_changed if the file has changed (or been deleted) since.
|
||||
Omit it to write unconditionally.
|
||||
create_only:
|
||||
type: boolean
|
||||
description: >-
|
||||
true writes only if nothing is at the path yet (409 file_exists
|
||||
otherwise), for making a new file without replacing one that
|
||||
appeared meanwhile. Cannot be combined with expect_sha256.
|
||||
responses:
|
||||
'200':
|
||||
description: File written.
|
||||
@@ -4251,7 +4289,11 @@ paths:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: Server is not stopped (not_stopped), or a restore, backup or file write already holds its world volume (maintenance_in_progress).
|
||||
description: >-
|
||||
The file changed since expect_sha256 was read (file_changed), something is
|
||||
already at the path with create_only (file_exists), the server is not
|
||||
stopped (not_stopped), or a restore, backup or file change already holds its
|
||||
world volume (maintenance_in_progress).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
@@ -4272,6 +4314,286 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
delete:
|
||||
tags: [files]
|
||||
operationId: deleteServerFile
|
||||
summary: Delete a file or folder in a server's world volume (owner-or-admin; server must be stopped).
|
||||
description: >-
|
||||
Deletes a file, a symlink (never what it points at) or a folder with
|
||||
everything in it. The world root itself is refused (400 bad_path). Same
|
||||
stopped-gate, world lock and os.Root containment as a write. The panel
|
||||
confirms first; this route does not. Audited as file.delete.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
security: [{ sessionCookie: [] }]
|
||||
parameters:
|
||||
- { name: name, in: path, required: true, schema: { type: string } }
|
||||
- name: path
|
||||
in: query
|
||||
required: true
|
||||
description: File or folder to delete, relative to the world root.
|
||||
schema: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: Deleted.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [path, status]
|
||||
properties:
|
||||
path: { type: string }
|
||||
status: { type: string, const: deleted }
|
||||
'400':
|
||||
description: Missing path, invalid server name, the world root, or a path that escapes it.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown server, or nothing at the path.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: Server is not stopped (not_stopped), or a restore, backup or file change already holds its world volume (maintenance_in_progress).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
'504':
|
||||
description: The file Job did not finish in time; retry.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/servers/{name}/files/mkdir:
|
||||
post:
|
||||
tags: [files]
|
||||
operationId: makeServerFolder
|
||||
summary: Make a folder in a server's world volume (owner-or-admin; server must be stopped).
|
||||
description: >-
|
||||
Makes one folder. Its parent must already exist (404), and nothing may be at
|
||||
the path yet (409 file_exists). Same stopped-gate, world lock and os.Root
|
||||
containment as a write. Audited as file.mkdir.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
security: [{ sessionCookie: [] }]
|
||||
parameters:
|
||||
- { name: name, in: path, required: true, schema: { type: string } }
|
||||
- name: path
|
||||
in: query
|
||||
required: true
|
||||
description: Folder to make, relative to the world root.
|
||||
schema: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: Folder made.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [path, status]
|
||||
properties:
|
||||
path: { type: string }
|
||||
status: { type: string, const: created }
|
||||
'400':
|
||||
description: Missing path, invalid server name, the world root, or a path that escapes it.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown server, or the parent folder does not exist.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: Something is already at the path (file_exists), the server is not stopped (not_stopped), or a restore, backup or file change already holds its world volume (maintenance_in_progress).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
'504':
|
||||
description: The file Job did not finish in time; retry.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/servers/{name}/files/rename:
|
||||
post:
|
||||
tags: [files]
|
||||
operationId: renameServerFile
|
||||
summary: Move or rename a file or folder in a server's world volume (owner-or-admin; server must be stopped).
|
||||
description: >-
|
||||
Moves the file or folder at path to to. It never replaces: an existing
|
||||
destination is 409 file_exists, and a missing destination folder is 404.
|
||||
server.properties, config/paper-global.yml and config/ cannot be moved under
|
||||
any name they are reached by (400 bad_path), because elsewhere the read path
|
||||
would no longer withhold their secrets. Same stopped-gate, world lock and
|
||||
os.Root containment as a write. Audited as file.rename.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
security: [{ sessionCookie: [] }]
|
||||
parameters:
|
||||
- { name: name, in: path, required: true, schema: { type: string } }
|
||||
- name: path
|
||||
in: query
|
||||
required: true
|
||||
description: File or folder to move, relative to the world root.
|
||||
schema: { type: string }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [to]
|
||||
properties:
|
||||
to: { type: string, minLength: 1, description: The new path, relative to the world root. }
|
||||
responses:
|
||||
'200':
|
||||
description: Moved.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [path, to, status]
|
||||
properties:
|
||||
path: { type: string }
|
||||
to: { type: string }
|
||||
status: { type: string, const: renamed }
|
||||
'400':
|
||||
description: Missing path or to, malformed body, invalid server name, the world root, a file felis manages, or a path that escapes the world root.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown server, nothing at path, or the destination folder does not exist.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: Something is already at to (file_exists), the server is not stopped (not_stopped), or a restore, backup or file change already holds its world volume (maintenance_in_progress).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
'504':
|
||||
description: The file Job did not finish in time; retry.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/servers/{name}/files/upload:
|
||||
put:
|
||||
tags: [files]
|
||||
operationId: uploadServerFile
|
||||
summary: Upload a file into a server's world volume (owner-or-admin; server must be stopped).
|
||||
description: >-
|
||||
Lands the raw request body as the file at path, up to 64 MiB — a plugin jar,
|
||||
a datapack, a world region. Content-Length is required (411
|
||||
length_required). An existing file is 409 file_exists unless overwrite=true;
|
||||
a folder at the path is 400 bad_path either way. The body is staged on
|
||||
felis-api's disk first and then fetched by the file Job with a one-time
|
||||
token, so the world lock is taken only after the body has arrived and a slow
|
||||
upload holds off no backup. The file lands atomically: a synced temporary
|
||||
sibling is checked against the staged size and SHA-256, then renamed into
|
||||
place, so a failed upload leaves the old file whole. Same stopped-gate and
|
||||
os.Root containment as a write. Audited as file.upload.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
security: [{ sessionCookie: [] }]
|
||||
parameters:
|
||||
- { name: name, in: path, required: true, schema: { type: string } }
|
||||
- name: path
|
||||
in: query
|
||||
required: true
|
||||
description: File to create, relative to the world root. Its folder must exist.
|
||||
schema: { type: string }
|
||||
- name: overwrite
|
||||
in: query
|
||||
required: false
|
||||
description: true replaces an existing file, keeping its mode. Anything else refuses to.
|
||||
schema: { type: string, enum: ["true", "false"] }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema: { type: string, format: binary }
|
||||
responses:
|
||||
'200':
|
||||
description: File uploaded.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [path, status, sha256, size]
|
||||
properties:
|
||||
path: { type: string }
|
||||
status: { type: string, const: uploaded }
|
||||
sha256: { type: string, pattern: '^[0-9a-f]{64}$', description: SHA-256 of the bytes landed. }
|
||||
size: { type: integer, format: int64, description: Bytes landed. }
|
||||
'400':
|
||||
description: >-
|
||||
Missing path, invalid server name, a folder or the world root at the path,
|
||||
a path that escapes the world root, or a body that ended before
|
||||
Content-Length bytes arrived (upload_incomplete).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown server, or the folder does not exist.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: A file is already at the path and overwrite is not true (file_exists), the server is not stopped (not_stopped), or a restore, backup or file change already holds its world volume (maintenance_in_progress).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'411':
|
||||
description: The request has no Content-Length (length_required).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'413':
|
||||
description: The file is over 64 MiB (too_large).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
'504':
|
||||
description: The file Job did not finish in time; retry.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'507':
|
||||
description: >-
|
||||
felis-api's staging disk has no room for the upload right now
|
||||
(upload_staging_full), or the world volume has no room for it
|
||||
(volume_full); nothing was changed.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
# ------------------------------------------------------ users (admin tier) ----
|
||||
/api/v1/users:
|
||||
|
||||
+77
-7
@@ -220,7 +220,7 @@ per-server cooldown → global running cap**. Map the API result:
|
||||
| HTTP | Code | Cause | Fix |
|
||||
|---|---|---|---|
|
||||
| `403` | `forbidden` | `autostartPolicy=allowlist` and UUID not allowlisted, or `ownerOnly` and caller is not owner | Add the UUID / claim the server / set `autostartPolicy=public` |
|
||||
| `409` | `maintenance_in_progress` | A restore, backup or file write holds the server's world volume (§3b) | Wait for the Job to finish |
|
||||
| `409` | `maintenance_in_progress` | A restore, backup or file change holds the server's world volume (§3b) | Wait for the Job to finish |
|
||||
| `409` | `world_reclaiming` | The idle reaper is archiving the world (§3b item 3); afterwards the server is released with an empty world | Nothing to wait for; the old world stays in the archive |
|
||||
| `429` | (cooldown) | Wake retried within the 30s per-server `WakeCooldown` | Wait out the cooldown |
|
||||
| `503` | `at_capacity` | Global `MaxRunningServers` cap reached | Stop another server or raise the cap |
|
||||
@@ -238,11 +238,11 @@ shortly.") lives in the Java plugin and is **[CODE-ONLY]** — the codes it reac
|
||||
to are produced by the Go-tested `authorizeWakeByUUID` / cooldown limiter, so
|
||||
grade the two halves separately.
|
||||
|
||||
### 3b. Wake, restore, backup or file save refused with `maintenance_in_progress`
|
||||
### 3b. Wake, restore, backup or file change refused with `maintenance_in_progress`
|
||||
|
||||
A server's world volume is ReadWriteOnce, and on a single node RWO lets a game
|
||||
pod and a restore Job mount it side by side. So felis-api serialises them per
|
||||
server: a restore, a backup, or a file write takes the world, and until its Job
|
||||
server: a restore, a backup, or a file change takes the world, and until its Job
|
||||
finishes every wake (panel or join) and every other world operation on that
|
||||
server gets `409 maintenance_in_progress`. File reads and listings never hold
|
||||
it. The operator applies the same rule when `desiredState` is flipped to
|
||||
@@ -253,7 +253,8 @@ What holds the world, in order:
|
||||
|
||||
1. An unfinished Job labelled `felis.lolicon.best/server=<name>` with
|
||||
`app.kubernetes.io/managed-by` `felis-restore`, `felis-backup`, or
|
||||
`felis-files` plus `felis.lolicon.best/files-mode=write`:
|
||||
`felis-files` with any `felis.lolicon.best/files-mode` but `list` or `read`
|
||||
(a save, new file, new folder, rename, delete or upload; §18):
|
||||
|
||||
```sh
|
||||
kubectl -n minecraft get jobs -l felis.lolicon.best/server=<name>
|
||||
@@ -281,7 +282,7 @@ What holds the world, in order:
|
||||
the lock before it deletes the world volume, keeps the world and retries the
|
||||
next day.
|
||||
|
||||
A restore, backup or file write refused with `409 not_stopped` although the
|
||||
A restore, backup or file change refused with `409 not_stopped` although the
|
||||
panel shows `Stopped` means the game pod is still terminating (its shutdown save
|
||||
can take a while); retry once `kubectl -n minecraft get pods -l
|
||||
felis.lolicon.best/server=<name>` shows nothing.
|
||||
@@ -465,7 +466,7 @@ installer (`sudo bash deploy/bootstrap.sh`) puts the value everywhere. [GO-TESTE
|
||||
stale. A proxy on another host is left alone: set `service-token` in its
|
||||
`felis-link.properties` to the value in Secret `felis/felis-service-token`, and
|
||||
it takes it within a few seconds.
|
||||
- **forwarding** — a server whose world a backup, restore or file write holds is
|
||||
- **forwarding** — a server whose world a backup, restore or file change holds is
|
||||
left running and named in the plan and the output; players cannot join it until
|
||||
it restarts, so stop and start it from the panel once that finishes. The next
|
||||
installer run restarts the proxy once more (its record of what the proxy was
|
||||
@@ -970,7 +971,7 @@ The reap sequence (all [GO-TESTED] hermetically) preserves the world unless a
|
||||
0. The world must be at rest before it is archived. A server still meant to
|
||||
run is told to stop (`desiredState: Stopped`) and left for the next run; one
|
||||
still stopping, whose game pod still exists, or whose world a restore,
|
||||
backup or file write holds is left too. Each of these counts in
|
||||
backup or file change holds is left too. Each of these counts in
|
||||
`awaiting_stop=` and does not fail the run. Once the server is down, the
|
||||
reaper takes the world's maintenance lock (§3b) and holds it through the
|
||||
archive and the volume delete: nothing can start the server or touch its
|
||||
@@ -2990,6 +2991,74 @@ for 10 seconds (the Free plan's limits).
|
||||
|
||||
---
|
||||
|
||||
## 18. Server files: a change or an upload is refused
|
||||
|
||||
The panel's Files page is for the server's owner or an admin, and only while
|
||||
the server is fully stopped. Each call runs a one-shot `felis files` Job in the
|
||||
`minecraft` namespace, labelled `app.kubernetes.io/managed-by=felis-files` and
|
||||
`felis.lolicon.best/files-mode=<list|read|write|mkdir|delete|rename|upload>`.
|
||||
A listing or a read holds nothing. Every change (a save, a new file or folder,
|
||||
a rename, a delete, an upload) holds the world for its Job (§3b), so a wake or a
|
||||
second change in the meantime gets `409 maintenance_in_progress`. The panel
|
||||
sends uploads one at a time and greys its other changes until they finish.
|
||||
|
||||
| Status | Code | Meaning | What to do |
|
||||
|---|---|---|---|
|
||||
| `409` | `not_stopped` | The game pod is still there, usually finishing its shutdown save | Retry once `kubectl -n minecraft get pods -l felis.lolicon.best/server=<name>` shows nothing |
|
||||
| `409` | `maintenance_in_progress` | Another change, a backup, a restore or the reaper holds the world | §3b |
|
||||
| `409` | `file_exists` | A new file, new folder, rename, or upload sent without replace found something at the path | Pick another name or clear the path; the panel offers Replace for an upload |
|
||||
| `409` | `file_changed` | The file changed after the editor read it | The editor offers to load the latest or overwrite it |
|
||||
| `400` | `bad_path` | The path leaves the world volume (`..`, an absolute path, a link pointing out), or it would move `server.properties`, `config` or `config/paper-global.yml`, or read `config/paper-global.yml` | Those three keep their names: the read path withholds their secrets by name, and `paper-global.yml` holds the proxy forwarding secret every server shares |
|
||||
| `404` | `not_found` | The path, or a new folder's parent, is gone | Refresh the listing |
|
||||
| `413` | `too_large` | A read over 1 MiB, a save over 256 KiB, or an upload over 64 MiB | Upload a large file whole instead of editing it |
|
||||
| `411` | `length_required` | An upload without `Content-Length` (a chunked body) | Upload from the panel, or with `curl -T`, which sends the length |
|
||||
| `400` | `upload_incomplete` | The body ended before its declared length | Retry; nothing was changed |
|
||||
| `507` | `upload_staging_full` | Staging this upload would leave felis-api's staging filesystem under 10% free | Free space on the uploads volume |
|
||||
| `507` | `volume_full` | The world volume ran out of space; the old file is left as it was | Delete files the server no longer needs, or grow its volume |
|
||||
| `504` | `files_timeout` | felis-api stopped waiting after 90 s | See below: the Job may still finish |
|
||||
| `503` | `files_unavailable` | felis-api runs without the file Job runner, or (for an upload) without a staging directory or its internal address | Check felis-api's startup log |
|
||||
|
||||
**An upload travels in two legs.** The browser sends the body to felis-api,
|
||||
which stages it under `/var/lib/felis/uploads/.file-staging` on the uploads
|
||||
volume (`felis-file-staging` in the pod's temp directory when that volume is not
|
||||
mounted). The Job then fetches it once from felis-api's internal face,
|
||||
`http://felis-api-internal.felis.svc.cluster.local:8081/api/v1/internal/file-uploads/<id>`,
|
||||
with a one-time token, checks the size and SHA-256, and lands it. The staged
|
||||
copy is deleted once the Job has answered, and a felis-api restart empties the
|
||||
directory. A Job that cannot fetch its upload, or fetches bytes that do not
|
||||
match, fails and leaves the target as it was; the panel shows a server error
|
||||
it can retry. The Job's log names the cause:
|
||||
|
||||
```sh
|
||||
kubectl -n minecraft get jobs -l felis.lolicon.best/server=<name>,app.kubernetes.io/managed-by=felis-files
|
||||
kubectl -n minecraft logs job/<job>
|
||||
```
|
||||
|
||||
**After `files_timeout`** the Job runs on to its own two-minute deadline and may
|
||||
still land the change. It keeps holding the world until it ends, so the next
|
||||
change waits on it with `maintenance_in_progress`; refresh the listing once it
|
||||
is gone to see whether the change landed. A Job is kept for two minutes after
|
||||
it ends, with its log.
|
||||
|
||||
Every change is audited as `file.write`, `file.mkdir`, `file.delete`,
|
||||
`file.rename` (with `to`) or `file.upload` (with `size_bytes`, `sha256` and
|
||||
`overwrite`), with `server_name` set to `<server>:<path>`:
|
||||
|
||||
```sh
|
||||
sudo k3s kubectl -n felis exec deploy/felis-postgres -c postgres -- psql -U postgres felis -c "
|
||||
SELECT created_at, actor, action, server_name, payload
|
||||
FROM audit_logs WHERE action LIKE 'file.%' ORDER BY created_at DESC LIMIT 20;"
|
||||
```
|
||||
|
||||
[GO-TESTED: `internal/fileedit`, `handlers_files_test.go`, `cmd/felis/files_test.go`,
|
||||
`internal/maintenance`.] [VM-TESTED: a pod labelled as a files Job in `minecraft`
|
||||
reaches `felis-api-internal:8081`; a 256 KiB save's content, split across six
|
||||
variables, lands byte for byte through the real binary, where one 140 KB variable
|
||||
fails with `argument list too long`. An upload through the API, both legs end to
|
||||
end, has not been run on a cluster.]
|
||||
|
||||
---
|
||||
|
||||
## Quick reference: symptom → section
|
||||
|
||||
| Symptom | Section |
|
||||
@@ -3033,3 +3102,4 @@ for 10 seconds (the Free plan's limits).
|
||||
| `FelisAuditWriteFailing` | §17 |
|
||||
| `felis breakGlass` sends no code / shows `Root override`; `otp_skipped` in the audit | §17 |
|
||||
| How long sessions, codes and audit rows are kept; export audit rows | §17 |
|
||||
| Files page: a change or upload refused (`file_exists`, `bad_path`, `too_large`, `upload_staging_full`, `volume_full`, `files_timeout`) | §18 |
|
||||
Reference in new issue
Block a user