refactor(server): drop the storage env-var tombstones — the variables never shipped

This commit is contained in:
jubnl
2026-08-20 21:52:35 +02:00
parent 17024381c5
commit c1312739ec
5 changed files with 7 additions and 72 deletions
-7
View File
@@ -78,13 +78,6 @@ is a *different* read of the same variable: `deriveSecurity()` reads it live
via `readEnv()`/`RuntimeEnvService`, so a value set after boot is observed on
the next call — it does not retroactively unlock the frozen cipher.)
**Tombstoned** (boot-aborting since the admin-config slice — configure storage
in the admin UI / `data/storage-config.json` instead): `TREK_S3_ENDPOINT`,
`TREK_S3_BUCKET`, `TREK_S3_ACCESS_KEY_ID`, `TREK_S3_SECRET_ACCESS_KEY`,
`TREK_S3_REGION`, `TREK_S3_KEY_PREFIX`, `TREK_S3_RETRIES`, `TREK_S3_TIMEOUT_MS`,
`TREK_UPLOADS_DIR` (see `storageTombstonePreconditions` in `env.ts`).
`TREK_PLACE_PHOTO_DIR` intentionally survives (documented, prod-supported) — do not tombstone it by association.
**Runtime-toggled** (read live on every access via `readEnv()` /
`RuntimeEnvService`; tests mutate these mid-lifetime):
TREK_MANAGED, PLACES_API_BASE, PLACES_API_KEY, MAPBOX_ACCESS_TOKEN, DEMO_MODE, NODE_ENV, APP_VERSION, APP_URL, TREK_API_DOCS_ENABLED,
+1 -27
View File
@@ -30,7 +30,7 @@ export function validateEnvAtBoot(raw: RawEnv = process.env as RawEnv): void {
const key = String(issue.path[0]);
return ` - ${key}=${JSON.stringify(raw[key])}: ${issue.message}`;
});
lines.push(...managedPreconditions(raw), ...storageTombstonePreconditions(raw));
lines.push(...managedPreconditions(raw));
if (lines.length === 0) return;
console.error(`Invalid environment configuration:\n${lines.join('\n')}`);
throw new Error(
@@ -68,29 +68,3 @@ function managedPreconditions(raw: RawEnv): string[] {
return problems;
}
/**
* Storage configuration moved wholesale to the admin UI / data/storage-config.json
* (spec: docs/superpowers/specs/2026-08-19-storage-admin-config-design.md).
* These variables are tombstoned rather than ignored: an upgrading operator
* with them set would otherwise silently run local-only — the exact
* half-configured trap the old all-or-nothing rule existed to prevent.
* TREK_PLACE_PHOTO_DIR intentionally survives (documented, prod-supported).
*/
const REMOVED_STORAGE_VARS = [
'TREK_S3_ENDPOINT',
'TREK_S3_BUCKET',
'TREK_S3_ACCESS_KEY_ID',
'TREK_S3_SECRET_ACCESS_KEY',
'TREK_S3_REGION',
'TREK_S3_KEY_PREFIX',
'TREK_S3_RETRIES',
'TREK_S3_TIMEOUT_MS',
'TREK_UPLOADS_DIR',
] as const;
function storageTombstonePreconditions(raw: RawEnv): string[] {
return REMOVED_STORAGE_VARS.filter((key) => Boolean(raw[key]?.trim())).map(
(key) => ` - ${key} was removed — configure storage in the admin UI or data/storage-config.json.`,
);
}
@@ -273,8 +273,8 @@ export class StorageRegistryService implements OnModuleInit {
const placePhotoDir = this.env.env().paths.placePhotoDir;
// 2. Built-in defaults; settings entries with the same name/category override.
// uploads-local's root is the computed default since TREK_UPLOADS_DIR was
// removed — relocation is a settings override row with the built-in's name.
// uploads-local's root is the computed default; relocation is a settings
// override row bearing the built-in's name.
const backends = new Map<string, BackendConfig>();
backends.set('uploads-local', { name: 'uploads-local', type: 'local', options: { root: DEFAULT_UPLOADS_ROOT } });
backends.set('backups-local', { name: 'backups-local', type: 'local', options: { root: DEFAULT_BACKUPS_ROOT } });
@@ -156,38 +156,6 @@ describe('validateEnvAtBoot — centrally administered preconditions', () => {
});
});
describe('validateEnvAtBoot — removed storage variables (tombstones)', () => {
const removed = [
'TREK_S3_ENDPOINT',
'TREK_S3_BUCKET',
'TREK_S3_ACCESS_KEY_ID',
'TREK_S3_SECRET_ACCESS_KEY',
'TREK_S3_REGION',
'TREK_S3_KEY_PREFIX',
'TREK_S3_RETRIES',
'TREK_S3_TIMEOUT_MS',
'TREK_UPLOADS_DIR',
] as const;
it.each(removed)('TOMB-001 %s present at boot aborts, naming the replacement', (key) => {
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
expect(() => validateEnvAtBoot({ [key]: 'anything' })).toThrow(
/Invalid environment configuration \(1 problem\)/,
);
const report = error.mock.calls.map((c) => c.join(' ')).join('\n');
expect(report).toContain(`${key} was removed`);
expect(report).toContain('admin UI or data/storage-config.json');
});
it('TOMB-002 TREK_PLACE_PHOTO_DIR survives (documented, prod-supported)', () => {
expect(() => validateEnvAtBoot({ TREK_PLACE_PHOTO_DIR: '/photos' })).not.toThrow();
});
it('TOMB-003 blank values are unset-equivalent and do not trip the tombstone', () => {
expect(() => validateEnvAtBoot({ TREK_UPLOADS_DIR: ' ', TREK_S3_BUCKET: '' })).not.toThrow();
});
});
describe('readEnv', () => {
it('reads process.env live — a runtime mutation is visible on the next call', () => {
const before = process.env.DEMO_MODE;
@@ -86,10 +86,10 @@ interface RegistryOpts {
}
/**
* TREK_UPLOADS_DIR is removed: the uploads-local root is the computed default,
* relocatable only via a settings override row bearing the built-in's name
* (first-class merge-by-name). The helper seeds that override so unit runs
* never write into the real server/uploads.
* The uploads-local root is the computed default, relocatable only via a
* settings override row bearing the built-in's name (first-class
* merge-by-name). The helper seeds that override so unit runs never write
* into the real server/uploads.
*/
function makeRegistry(opts: RegistryOpts = {}) {
const uploadsRoot = opts.uploadsRoot ?? makeTmpDir();