backup engine internals - restic timeout and stack-dir capture

This commit is contained in:
jarek committed 2026-08-13 10:20:30 +02:00
1 parent 610e46dc19
commit 3c75bd4919
9 files changed
+113 -152

No files matched your search

+23 -7
View File
@@ -41,7 +41,9 @@ export interface MetadataFile {
* @param resticArgs the full restic argv (starting with 'backup') — already
* tag/exclude/flag-assembled by the caller.
*/
export function buildBackupScript(resticArgs: string[], stackDirProbe?: { volumeKey: string; composeFileName: string; label?: string; hostPath?: string }): string {
import type { StackDirProbeHint } from './stackdir-plan';
export function buildBackupScript(resticArgs: string[], stackDirProbe?: { volumeKey: string; composeFileName: string; label?: string; hostPath?: string; hint?: StackDirProbeHint }): string {
// restic runs backgrounded so the SIGINT-forwarding trap can reach it on cancel
// (SIGINT lets restic release its repo lock; a SIGKILL orphans it and hangs the
// next backup on --retry-lock). The kill-0 re-wait loop only fires when a signal
@@ -61,14 +63,28 @@ export function buildBackupScript(resticArgs: string[], stackDirProbe?: { volume
if (stackDirProbe) {
const composePath = shellQuote(`/volumes/${stackDirProbe.volumeKey}/${stackDirProbe.composeFileName}`);
const label = stackDirProbe.label ? ` ${stackDirProbe.label}` : '';
// Name the REAL host path we mounted (<remote_stacks_dir>/<stack>) so the operator knows
// exactly where to look, and what to do: redeploy the stack to stage its files there.
const hostHint = stackDirProbe.hostPath
? ` The stack folder is empty or missing at ${stackDirProbe.hostPath} on the Docker host - redeploy the stack to stage its files there.`
: '';
// The compose is missing under the host dir we bind-mounted. For a remote env (hawser or a
// user-set path) tell the operator to point "Remote stack path (for backup)" at the real host
// path; for a local stack a redeploy stages the files.
const hint = stackDirProbe.hint;
const remote = hint?.kind === 'hawser-defaulted' || hint?.kind === 'user-set';
const where = remote ? hint.hostPath : stackDirProbe.hostPath;
// "on <env>" when we know the environment name, else a plain "on the host".
const on = remote && hint.envName ? ` on ${hint.envName}` : ` on the host`;
// Name the EXACT settings location so the operator can go straight there.
const settingsLoc = remote && hint.envName ? `Settings > Environments > ${hint.envName}` : `Settings > Environments`;
const fix = remote
? ` Set "Remote stack path (for backup)" in ${settingsLoc} to the real host path where this stack's files live.`
: ` Redeploy the stack to stage its files there.`;
const at = where ? ` in ${where}` : '';
// Build the whole message as ONE string and shell-quote it so the operator-facing text
// (which contains quotes, parens, and `>` - e.g. `"Remote stack path (for backup)"` and
// `Settings > Environments > <env>`) can never break the `sh` syntax. The timestamp stays
// a live $(date ...) OUTSIDE the quoted literal, concatenated in.
const msg = `${label} STACKDIR PROBE FAILED: compose ${stackDirProbe.composeFileName} not found${at}${on}.${fix}`;
probe =
`if [ ! -f ${composePath} ]; then ` +
`echo "[backup] $(date -u +%Y-%m-%dT%H:%M:%SZ)${label} STACKDIR PROBE FAILED: the stack's compose (${stackDirProbe.composeFileName}) is not present under the mounted stack dir.${hostHint} Refusing to write a silent-empty snapshot." 1>&2; ` +
`echo "[backup] $(date -u +%Y-%m-%dT%H:%M:%SZ)"${shellQuote(msg)} 1>&2; ` +
`exit 1; fi; `;
}