A data archive or restore ran inside its request, which sent nothing until tar finished: Bun closes a silent socket after 10s (its default idleTimeout in production) and a proxy cuts it at about 100s, so the page showed a network error while the archive kept going, and a retry said another backup was already running. The routes now start the run (startDataBackupRun, refused while the backup lock is held) and answer at once. The page follows it by polling api/admin/backups/data-status: an archive reloads once written, a restore once the status comes from a new process after its restart, and a failure shows its error. A run in progress survives a reload. Co-Authored-By: Claude Opus 5.5 <[email protected]>
34 lines
796 B
TypeScript
34 lines
796 B
TypeScript
let is_running = false;
|
|
|
|
type Params<T> = {
|
|
task: () => Promise<T>;
|
|
};
|
|
|
|
/**
|
|
* Whether a backup or restore of any kind holds the lock right now, so a
|
|
* background run can be refused before it starts.
|
|
*/
|
|
export function isBackupRunning() {
|
|
return is_running;
|
|
}
|
|
|
|
/**
|
|
* Runs one backup or restore at a time, whichever kind it is, so a
|
|
* database snapshot, a database restore and a whole-data archive never
|
|
* overlap and never fight over the same files. Hands back what the task
|
|
* returned.
|
|
*/
|
|
export default async function withBackupRunLock<T>({ task }: Params<T>) {
|
|
if (is_running) {
|
|
throw new Error(`Another backup or restore is already running`);
|
|
}
|
|
|
|
is_running = true;
|
|
|
|
try {
|
|
return await task();
|
|
} finally {
|
|
is_running = false;
|
|
}
|
|
}
|