Files
TREK/server/src/app-config/data-paths.ts
T
Maurice bef8fc20f1 fix(storage): reset reclaim state after any restore, re-check owners often, share release ticks
- A restore (admin panel or RESTORE_FROM_BACKUP) now leaves
  data/.trek-reclaim-reset before it swaps the database in. Boot reconcile
  resets the reclaim state when it finds the file, whatever install id the
  database names, and removes it afterwards; a successful reopen removes it
  too. A restart after a failed reopen, or a boot restore of this
  installation's own backup, no longer keeps the old switch, marks and
  confirmations. The id mismatch reset stays.
- The release job skips a tick (install_mismatch) while the database names
  another installation or a reset is still pending.
- An allowed owner verdict is cached for 15 minutes, a refusal for 26 hours.
  The local driver reads a list prefix as a folder, so the category root is
  still listed whole, at most once per category per window.
- Each tick reads every allowed category's due rows separately, an equal
  share of the 200 each, so a category whose deletes keep failing cannot
  starve the others.
- Rows dropped after 30 days overdue and releases refused by a full queue
  are logged at warn with their counts.
2026-11-01 18:31:41 +01:00

80 lines
3.3 KiB
TypeScript

import { readEnv, type AppEnv } from './env';
import path from 'node:path';
/**
* `<server>/`, the directory holding package.json. `src/app-config` and
* `dist/app-config` both sit two levels below it, so the anchor is the same
* under vitest, `npm run dev` and the built image.
*/
export const SERVER_ROOT = path.resolve(__dirname, '..', '..');
/**
* Where TREK keeps what it writes. In the image `data/` and `uploads/` are
* symlinks into `/app`, which is why every consumer used to anchor on its own
* `__dirname` and why none of them may use `process.cwd()` instead.
*/
export interface DataPaths {
/** `<server>/data`: the database, the key files, backups, logs and plugin trees. */
readonly dataDir: string;
/** `<server>/uploads`: the local upload categories (files, covers, photos, ...). */
readonly uploadsDir: string;
/** `data/backups`: archives written by the manual and the automatic backup. */
readonly backupsDir: string;
/** `data/tmp`: driver-agnostic scratch space (restore spool, mirror staging). */
readonly tmpDir: string;
/** `data/logs`: the rotating `trek.log`. */
readonly logsDir: string;
/** `data/.jwt_secret`: written on first start and by the admin's JWT rotation. */
readonly jwtSecretFile: string;
/** `data/.encryption_key`: the at-rest key when ENCRYPTION_KEY does not provide it. */
readonly encryptionKeyFile: string;
/**
* `data/.trek-install-id`: this installation's random id, written on first
* start. It never enters a backup and no restore writes it, so a database
* restored or copied from another install is told apart by it (storage reclaim).
*/
readonly installIdFile: string;
/**
* `data/.trek-reclaim-reset`: left by a restore right before it swaps the
* database in, removed once storage reclaim has dropped the state the swapped
* database brought. A start that finds it resets that state first, so a
* restore whose reopen failed, or one at boot, is covered even when the
* backup came from this installation. Never part of a backup.
*/
readonly reclaimResetFile: string;
/**
* The SQLite file a non-test process opens: TREK_DB_FILE when set, else
* `data/travel.db`. Under NODE_ENV=test the connection is `:memory:` instead
* (db/db-path.ts), which is a property of the connection, not of the layout.
*/
readonly dbFile: string;
}
/**
* The data layout, derived from the server root and the live environment.
*
* The only variable involved is TREK_DB_FILE, read through `readEnv()` like
* every other live value, so a test that points it somewhere else is seen on
* the next call. Nothing here touches the disk: creating a directory is the
* caller's decision.
*/
export function resolveDataPaths(
env: { db: Pick<AppEnv['db'], 'trekDbFile'> } = readEnv(),
root: string = SERVER_ROOT,
): DataPaths {
const dataDir = path.join(root, 'data');
return {
dataDir,
uploadsDir: path.join(root, 'uploads'),
backupsDir: path.join(dataDir, 'backups'),
tmpDir: path.join(dataDir, 'tmp'),
logsDir: path.join(dataDir, 'logs'),
jwtSecretFile: path.join(dataDir, '.jwt_secret'),
encryptionKeyFile: path.join(dataDir, '.encryption_key'),
installIdFile: path.join(dataDir, '.trek-install-id'),
reclaimResetFile: path.join(dataDir, '.trek-reclaim-reset'),
dbFile: env.db.trekDbFile || path.join(dataDir, 'travel.db'),
};
}