backups: seconds in the name, so two in one minute don't overwrite; update.sh prints the api's log on a failed check
This commit is contained in:
1 parent
ff59bc7799
commit
e2518b03d8
6 files changed
+17
-8
No files matched your search
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Backups (04 §8.1): BACKUPS_DIR/selfphoneng-YYYYMMDD-HHMM.tar.gz with db.sql (pg_dump), the
|
||||
// Backups (04 §8.1): BACKUPS_DIR/selfphoneng-YYYYMMDD-HHMMSS.tar.gz with db.sql (pg_dump), the
|
||||
// voicemail, custom sounds and music-on-hold folders, optionally recordings, and manifest.json.
|
||||
// No generated Asterisk config (plaintext SIP passwords; it's rebuilt from the DB on start).
|
||||
// Restore is scripts/restore.sh only.
|
||||
@@ -17,7 +17,9 @@ import { getSetting } from './settings.ts';
|
||||
const run = promisify(execFile);
|
||||
/** manifest.json `version`: the backup format, which restore.sh checks. */
|
||||
export const BACKUP_FORMAT = 1;
|
||||
export const BACKUP_NAME = /^selfphoneng-\d{8}-\d{4}\.tar\.gz$/;
|
||||
/** With seconds since 2026-10-21, so two backups in one minute (the update's and the api's own
|
||||
* before migrating) don't share a name; older names have none. */
|
||||
export const BACKUP_NAME = /^selfphoneng-\d{8}-\d{4}(\d{2})?\.tar\.gz$/;
|
||||
|
||||
/**
|
||||
* Lets restore.sh tell whether .env has the APP_SECRET the backup's encrypted columns need,
|
||||
@@ -70,7 +72,9 @@ export async function createBackup(
|
||||
src: BackupSources,
|
||||
opts: { keep: number; now?: Date },
|
||||
): Promise<BackupDto> {
|
||||
const name = `selfphoneng-${stamp(src.timezone, opts.now)}.tar.gz`;
|
||||
const now = opts.now ?? new Date();
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
const name = `selfphoneng-${stamp(src.timezone, now)}${seconds}.tar.gz`;
|
||||
const work = await mkdtemp(join(tmpdir(), 'sp-backup-'));
|
||||
const tmp = join(src.outDir, `.${name}.tmp`);
|
||||
try {
|
||||
@@ -99,7 +103,7 @@ export async function createBackup(
|
||||
}
|
||||
const manifest = {
|
||||
version: BACKUP_FORMAT,
|
||||
createdAt: (opts.now ?? new Date()).toISOString(),
|
||||
createdAt: now.toISOString(),
|
||||
includesRecordings: included.includes('recordings'),
|
||||
folders: included,
|
||||
secretCheck: secretCheck(src.appSecret),
|
||||
|
||||
@@ -50,14 +50,17 @@ describe.skipIf(!hasTestDb)('backup routes', () => {
|
||||
it('lists backups newest first, ignoring other files', async () => {
|
||||
writeFileSync(join(dir(), 'selfphoneng-20261018-0330.tar.gz'), 'old');
|
||||
writeFileSync(join(dir(), 'selfphoneng-20261019-0330.tar.gz'), 'newer');
|
||||
// with seconds (from 2026-10-21): a second one in the same minute sorts after it, newer
|
||||
writeFileSync(join(dir(), 'selfphoneng-20261019-033045.tar.gz'), 'newest');
|
||||
writeFileSync(join(dir(), '.selfphoneng-20261020-0330.tar.gz.tmp'), 'half-written');
|
||||
writeFileSync(join(dir(), 'notes.txt'), 'x');
|
||||
const r = await req('GET', '/api/system/backups');
|
||||
expect(r.json().map((b: { name: string }) => b.name)).toEqual([
|
||||
'selfphoneng-20261019-033045.tar.gz',
|
||||
'selfphoneng-20261019-0330.tar.gz',
|
||||
'selfphoneng-20261018-0330.tar.gz',
|
||||
]);
|
||||
expect(r.json()[0].size).toBe(5);
|
||||
expect(r.json()[1].size).toBe(5);
|
||||
});
|
||||
|
||||
it('downloads one by name, nothing else', async () => {
|
||||
|
||||
Reference in new issue
Block a user