Phase 0 of the migration-system refresh. Replaces the 77-line
scripts/migrate/ with a properly-shaped kernel that Phase 1 (WebUI) and
Phase 3 (direct peer SSH) can both build on.
New module layout (6 files):
migrate_progress.sh — migrateEmit JSON-per-line helper; opt-in via
MIGRATE_JSON_PROGRESS=1, writes to fd 3 if open
(clean WebUI streaming channel) else stdout.
migrate_discover.sh — migrateDiscoverHosts / migrateDiscoverApps /
migrateDiscoverAppDetail (JSON {snapshots, latest_*}).
Old migrateDiscoverAppsForHost kept as back-compat.
migrate_preflight.sh — migratePreflight emits one JSON object with
snapshot{id,date}, destination{installed,running,
disk_free_kb}, collision{occurs,default_action,
pre_backup_default}, url_rewrite{default_action,
per_app_opt_out}, warnings[], errors[].
Exit 0 on usable preflight, 1 on hard error.
migrate_url_rewrite.sh— Host-bound CFG_<APP>_* fields (URL/HOST/DOMAIN/
DOMAIN_PREFIX/HOSTNAME/PUBLIC_URL) get rewritten
from the destination's install-template after
restore — so a moved app stops claiming the
source's hostnames. Per-app opt-out via
CFG_<APP>_MIGRATE_URL_REWRITE=false. All other
fields (DB passwords, API keys, prefs) carry
over from the source unchanged.
migrate_pre_backup.sh — migratePreBackupDestination takes a snapshot of
the destination's existing <app> (tagged
pre-migrate=<UTC timestamp>) before the wipe.
Default ON; opt-out with --no-pre-backup. Safety
net for the always-replace collision policy.
migrate_apply.sh — migrateApplyApp / migrateApplySystem. Parses
--no-pre-backup / --keep-urls / --json-progress
opts, runs preflight → pre-backup → restoreAppStart
(existing flow) → URL rewrite → re-deploy compose.
migrateApp / migrateSystem kept as shims so the
old CLI surface still works.
CLI dispatcher (cli_restore_commands.sh + cli_restore_header.sh):
Existing 'restore migrate app/system/discover' calls all still work.
New verbs:
restore migrate list <host> [loc_idx]
restore migrate preflight <host> <app> [loc_idx] ← JSON, for the WebUI
Design choices baked in (per the spec):
- Always-replace collision (no multi-install of an app), safety net is the
on-by-default pre-migrate backup.
- URL rewrite by host-bound suffix list, not per-field allowlist — works
out-of-the-box for new apps without extra config.
- migrateEmit fd-3 contract is what Phase 1's WebUI will stream; falls
back to stdout in interactive CLI so dev/debug just works.
- Transport-agnostic: nothing in this kernel knows whether the backup
location is local/SSH/S3/Connect — engineSnapshotsJson + engineBackupApp
do that, so Connect (the future blind-relay) plugs in as 'just another
location kind' with zero kernel changes.
Smoke-tested: all 13 public functions register; JSON emit produces correct
escaping (quoted strings vs bare numerics) and respects MIGRATE_JSON_PROGRESS.
Signed-off-by: librelad <librelad@digitalangels.vip>
58 lines
2.3 KiB
Bash
58 lines
2.3 KiB
Bash
#!/bin/bash
|
|
|
|
# Pre-migrate backup. Before the destination app gets wiped and replaced with
|
|
# the source's snapshot, take a fresh local backup of the destination's app —
|
|
# tagged with `pre-migrate=<timestamp>` so it's easy to find for rollback if
|
|
# the migrate misbehaves. Best-effort: emits a notice and continues if no
|
|
# backup location is available (the caller already warned the user via
|
|
# preflight).
|
|
#
|
|
# Args: <app_name> <loc_idx> (loc_idx defaults to first enabled)
|
|
# Returns: 0 always (a missing pre-backup must never block the migrate; the
|
|
# user already saw and confirmed the preflight that warned about it).
|
|
|
|
migratePreBackupDestination()
|
|
{
|
|
local app="$1"
|
|
local idx="$2"
|
|
|
|
if [[ -z "$app" ]]; then
|
|
isError "migratePreBackupDestination: app required"
|
|
migrateEmit phase=pre-backup status=skipped reason=no-app
|
|
return 0
|
|
fi
|
|
|
|
# Only meaningful if the destination actually has this app installed.
|
|
if [[ ! -d "$containers_dir$app" ]]; then
|
|
isNotice "No existing $app on destination — pre-migrate backup skipped"
|
|
migrateEmit phase=pre-backup status=skipped reason=no-existing-app app="$app"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -z "$idx" ]]; then
|
|
idx=$(resticEnabledLocations | head -1)
|
|
fi
|
|
if [[ -z "$idx" ]]; then
|
|
isNotice "No backup locations enabled — pre-migrate backup skipped"
|
|
migrateEmit phase=pre-backup status=skipped reason=no-backup-location app="$app"
|
|
return 0
|
|
fi
|
|
|
|
local stamp
|
|
stamp=$(date -u +%Y%m%dT%H%M%SZ)
|
|
isNotice "Pre-migrate backup of destination $app → $(resticLocationName "$idx") (tag pre-migrate=$stamp)"
|
|
migrateEmit phase=pre-backup status=running app="$app" loc_idx="$idx" stamp="$stamp"
|
|
|
|
# engineBackupApp takes (idx, app, [extra_tags...]). The pre-migrate tag
|
|
# makes the snapshot trivially findable later: restic snapshots --tag pre-migrate
|
|
if engineBackupApp "$idx" "$app" "pre-migrate=$stamp"; then
|
|
isSuccessful "Pre-migrate backup of $app complete"
|
|
migrateEmit phase=pre-backup status=complete app="$app" stamp="$stamp"
|
|
else
|
|
isNotice "Pre-migrate backup of $app FAILED — continuing migrate anyway (you confirmed)"
|
|
migrateEmit phase=pre-backup status=failed app="$app" stamp="$stamp"
|
|
fi
|
|
|
|
return 0
|
|
}
|