# Pilot photo storage operations

Configure MEDIA_ROOT as an absolute private persistent directory, for example D:/cmms-data/photos on Windows or /var/lib/cmms/photos on Linux. Provision access only for the API service account. The directory must survive restarts/deployments and must not be a public/static, source, generated, dist, or symlinked directory. The server does not expose this directory statically. An absent setting produces SERVICE_UNAVAILABLE only for photo operations; it is a deployment prerequisite.

This adapter supports one API process and one persistent volume. Do not run multiple replicas or an independent cleanup process concurrently with active uploads. Use the API-owned cleanup function in-process, or stop/drain API mutations before running the operator CLI:
`pnpm --filter express-api-starter-ts media:cleanup`

Files use server-generated random keys and are reencoded to WebP without source metadata. The input limit is 2 MiB / 20 million pixels, with exact JPEG/PNG/WebP MIME matching and no animation. Reads require authentication and active entities, use private/no-store and nosniff, and never fetch remote URLs.

Replacement/removal conditionally matches version and increments it; metadata/audit commit together. Old files are retained until conservative cleanup. Known rolled-back uploads can be discarded immediately. When database commit acknowledgement is uncertain, the staged file is retained and made eligible for later reference-checked cleanup rather than risking deletion of a committed photo. Cleanup ignores recent files (24-hour grace), staged files with persistent .stage markers, symlinks, unknown filenames, and all database-referenced keys including archived records. Database failures abort cleanup. Keys are never reused or supplied by clients.

## Backup, restore and rollback

1. Drain mutation requests and pause cleanup.
2. Back up MySQL and the entire media volume in the same maintenance window. Preserve permissions and record database backup time and application version.
3. Restore into an isolated environment: restore database and media together, configure MEDIA_ROOT, check authenticated reads and a versioned replacement, then verify preflight.
4. Resume writes only after the pair is verified. Retain the earlier backup for recovery.
5. Roll back application routes/UI to a schema-compatible version, preserving new columns, ACCEPTED reports, unique report/WO links and media files. Do not run destructive down migrations or return to code that writes incompatible lifecycle values.

The tests use temporary private directories and isolated databases. Their results do not prove that a production volume is mounted, production backups exist, or deployment migration has been applied; those must be verified for the actual target before release.

Staging markers also protect files across adapters/processes. A crash can leave a marker; retain it conservatively. Remove abandoned markers only during a maintenance window after stopping upload processes and verifying database references, then run cleanup.
