feat: manage uploaded image retention
This commit is contained in:
@@ -134,7 +134,7 @@ Never give either caller `image_admin_secret`, which also authorizes explicit
|
||||
branch changes. This fallback handles webhook delivery outages; if the image
|
||||
service itself is stopped, restore it and run the caller command again.
|
||||
|
||||
### Short-lived user-icon uploads
|
||||
### User uploads
|
||||
|
||||
Core and Core2026 can store validated account icons and editor attachments
|
||||
through this service with
|
||||
@@ -158,10 +158,59 @@ the matching `image_upload_core_secret` or `image_upload_core2026_secret` on the
|
||||
game server. The shared secrets stay server-side in Docker secrets; they are
|
||||
not returned to browsers or forwarded to Cloudflare.
|
||||
|
||||
Run `deploy/scripts/init-secrets.sh` before the first Compose start so
|
||||
`runtime-data/uploads` exists with permissions that allow the hook container to
|
||||
write and the Nginx container to read. Back up this directory independently of
|
||||
the Git repository when moving servers.
|
||||
Run `deploy/scripts/init-secrets.sh` before the first Compose start so the upload
|
||||
directory exists with permissions that allow the hook container to write and
|
||||
the Nginx container to read. `IMAGE_UPLOAD_PATH` can point only the uploaded
|
||||
images at a separate ZFS dataset. Keep `IMAGE_RUNTIME_PATH` on SSD storage for
|
||||
the small SQLite database and service state. Back up both paths independently
|
||||
of the Git repository when moving servers.
|
||||
|
||||
### Retention and image administration
|
||||
|
||||
Account icons below `/icons/users/` are permanent. They are inventoried in the
|
||||
metadata database but never become deletion candidates. Tiptap images below
|
||||
`/uploads/` become candidates after 730 days without a request reaching the
|
||||
origin Nginx. Nginx mirrors a small internal notification to Node for those
|
||||
requests; Node deduplicates paths in memory and writes each path to SQLite at
|
||||
most once per flush interval (60 seconds by default).
|
||||
|
||||
Cloudflare and browser cache hits do not necessarily reach the origin. The
|
||||
two-year interval is therefore an operational expiry policy, not proof that an
|
||||
old HTML document no longer contains the URL. Confirm that the Cloudflare edge
|
||||
TTL does not exceed one year before enabling this policy.
|
||||
|
||||
Candidates remain publicly available until an administrator quarantines them.
|
||||
Quarantine moves the file into a hidden directory on the same upload bind and
|
||||
is reversible. Permanent deletion is disabled until the 30-day quarantine
|
||||
period has elapsed. A request received while an image is only a candidate
|
||||
returns it to active state; requests do not restore quarantined files.
|
||||
|
||||
Open the minimal administration panel at:
|
||||
|
||||
```text
|
||||
https://sam-image.hided.net/admin/
|
||||
```
|
||||
|
||||
Log in with the value in the ignored `secrets/image_admin_panel_password` file.
|
||||
The panel can filter and preview assets, show storage and retention timestamps,
|
||||
quarantine candidates, restore quarantined files, and permanently delete files
|
||||
whose grace period has elapsed. It uses a separate short-lived, HttpOnly,
|
||||
SameSite session; the stronger `image_admin_secret` used for branch deployment
|
||||
is never sent to the browser. Rotate the panel password by replacing its secret
|
||||
file and recreating `image-hook`.
|
||||
|
||||
Retention settings are available in `.env`:
|
||||
|
||||
```dotenv
|
||||
IMAGE_CONTENT_RETENTION_DAYS=730
|
||||
IMAGE_CONTENT_QUARANTINE_DAYS=30
|
||||
IMAGE_ASSET_TOUCH_FLUSH_SECONDS=60
|
||||
IMAGE_ASSET_MAINTENANCE_SECONDS=21600
|
||||
```
|
||||
|
||||
The SQLite database uses WAL and synchronous writes. Back it up with the service
|
||||
stopped or with a SQLite-aware backup; do not copy only the main `.sqlite3` file
|
||||
while the service is running because committed data can still be in `-wal`.
|
||||
|
||||
Legacy HTTP mutation is disabled by default. An emergency PHP rollback must
|
||||
first stop `image-hook`, then create the ignored `hook/legacy-enabled` sentinel
|
||||
@@ -181,9 +230,13 @@ curl -fsS https://sam-image.hided.net/game/back.jpg -o /dev/null
|
||||
curl -fsS https://sam-image.hided.net/image/icons/default.jpg -o /dev/null
|
||||
```
|
||||
|
||||
Nginx serves only `game/`, `icons/`, `hook/list.json`, and
|
||||
`hook/inventory.v2.json`. The API inventory additionally reports the deployed
|
||||
branch, full commit, generation time, asset list, and both public base URLs.
|
||||
Then log in to `/admin/` and verify that an existing account icon is shown as
|
||||
permanent (`user-icons`, `active`) and a Tiptap upload is shown as `content`.
|
||||
|
||||
Nginx serves the static `game/`, `icons/`, and `uploads/` paths, the two public
|
||||
inventory files, and the authenticated `/admin/` panel. The API inventory
|
||||
additionally reports the deployed branch, full commit, generation time, asset
|
||||
list, and both public base URLs.
|
||||
|
||||
### Explicit branch deployment
|
||||
|
||||
@@ -195,7 +248,9 @@ signed administration command:
|
||||
./deploy/scripts/admin-deploy.sh <branch> [expected-commit]
|
||||
```
|
||||
|
||||
The administration route is not exposed through the public reverse proxy.
|
||||
The signed branch-deployment route under `/v1/admin/` is not exposed through
|
||||
the public reverse proxy. It is separate from the password-authenticated image
|
||||
management panel at `/admin/`.
|
||||
|
||||
### Tests and rollback
|
||||
|
||||
|
||||
Reference in New Issue
Block a user