swap-storage
Point file storage somewhere other than the in-cluster RustFS (another S3-compatible server or service) or add one without an S3 API. Use when the user decides to change where uploads live. Not for de
SKILL.md.claude/skills/swap-storage/SKILL.md
GitHubSwap object storage
- Interface:
Storage(presigned upload and download URLs,head,read,write,move,delete) inpackages/nest-common/src/storage.ts. - Today:
S3Storage, built bycreateStorage(env)in the same file (null whenS3_BUCKETis unset: uploads are off). It covers RustFS (locally and in every cluster, from the data chart) and any other S3 API: AWS S3, R2, GCS (XML API with HMAC keys), Azure behind an S3 gateway. - Provided as
STORAGEinapps/api/src/modules/files/files.module.ts(signs uploads) andapps/worker/src/files/files.module.ts(checks and moves them). - Env:
S3_BUCKET,S3_REGION,S3_ENDPOINT,S3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_FORCE_PATH_STYLE(storageEnvinpackages/nest-common/src/env.ts), andSTORAGE_ORIGINfor the web app's CSP.
Another S3-compatible provider
No code: set the S3_* variables for api and worker (their SOPS Secrets, in place of the
data chart's storage Secret), STORAGE_ORIGIN for web, and give the bucket a CORS rule
allowing PUT and GET from the site's origin (browsers upload directly; see s3-init
in docker-compose.yml and the data chart's bucket Job). Everything is self-hosted on
purpose: check with the user before moving uploads to a managed service.
A provider without an S3 API
- A class implementing
Storage. Presigned uploads must bind content type and length so a client can't upload something else;readmust refuse objects overmaxByteswithout buffering them. - Choose it in
createStoragefrom a new variable instorageEnv, documented in.env.exampleanddocs/environment.md. Callers don't change.
Tests
packages/nest-common/test/storage.integration.test.ts runs against RustFS
(bun run test:integration starts it); the upload flow is covered in
apps/api/test/api.integration.test.ts, the worker's uploads tests in
apps/worker/test/worker.integration.test.ts, and apps/web/e2e/avatar.spec.ts.
Finish
- The verify skill.
- Ask the
revieweragent to review the change, and thesecurity-revieweragent: a new implementation brings its own credentials and sends data somewhere new. If you wrote a migration, themigration-revieweragent too. - Update the seam's row in the README's "Scaling path" table if what's "Now" changed.