Merge claude/2
This commit is contained in:
commit
64a0509ea9
@ -331,17 +331,26 @@ class BackupPage {
|
||||
|
||||
async refreshAll() {
|
||||
const ts = Date.now();
|
||||
const [dashboard, locations, , schema, migrate] = await Promise.all([
|
||||
const [dashboard, locations, , schema, migrate, peersData] = await Promise.all([
|
||||
this.fetchJson(`/data/backup/generated/dashboard.json?t=${ts}`),
|
||||
this.fetchJson(`/data/backup/generated/locations.json?t=${ts}`),
|
||||
this.loadSystemConfigs(),
|
||||
this.fetchJson(`/data/backup/generated/schema.json?t=${ts}`),
|
||||
this.fetchJson(`/data/backup/generated/migrate.json?t=${ts}`)
|
||||
this.fetchJson(`/data/backup/generated/migrate.json?t=${ts}`),
|
||||
this.fetchJson(`/data/peers/generated/peers.json?t=${ts}`)
|
||||
]);
|
||||
this.dashboard = dashboard;
|
||||
this.locations = locations;
|
||||
this.locSchema = schema;
|
||||
this.migrate = migrate;
|
||||
// Build hostname → friendly-name lookup once so renderMigrate can show
|
||||
// "homelab (host: homelab.lan)" instead of bare hostnames.
|
||||
this.hostnameToPeerName = {};
|
||||
for (const p of (peersData?.peers || [])) {
|
||||
if (p.kind === 'backup-channel' && p.config?.hostname) {
|
||||
this.hostnameToPeerName[p.config.hostname] = p.name;
|
||||
}
|
||||
}
|
||||
this.snapshotsByLoc = {};
|
||||
|
||||
if (!this.engines.length) await this.loadEngines();
|
||||
@ -1811,11 +1820,16 @@ class BackupPage {
|
||||
<h3 style="margin:0">${this.escape(loc.name || 'Location')}</h3>
|
||||
<span class="backup-card-hint">${(loc.hosts || []).length} other host${loc.hosts.length === 1 ? '' : 's'} backing up here</span>
|
||||
</div>
|
||||
${loc.hosts.map(host => `
|
||||
${loc.hosts.map(host => {
|
||||
const peerName = (this.hostnameToPeerName || {})[host.hostname];
|
||||
const headerLabel = peerName
|
||||
? `<strong style="font-size:1.05em">${this.escape(peerName)}</strong><span class="backup-card-hint" style="margin-left:6px; font-size:.85em">host: <code>${this.escape(host.hostname)}</code></span>`
|
||||
: `<strong style="font-size:1.05em">${this.escape(host.hostname)}</strong>`;
|
||||
return `
|
||||
<div class="backup-migrate-host" style="border:1px solid var(--border-color, #2a2a2a); border-radius:8px; padding:14px; margin-bottom:12px">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:10px">
|
||||
<div>
|
||||
<strong style="font-size:1.05em">${this.escape(host.hostname)}</strong>
|
||||
${headerLabel}
|
||||
<span class="backup-card-hint" style="margin-left:10px">${(host.apps || []).length} app${host.apps.length === 1 ? '' : 's'} available</span>
|
||||
</div>
|
||||
<button class="backup-primary-btn" data-action="migrate-host"
|
||||
@ -1846,7 +1860,8 @@ class BackupPage {
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
|
||||
@ -81,6 +81,9 @@ migrateApplyApp()
|
||||
migrateEmit phase=pre-backup status=skipped reason=user-opt-out app="$app"
|
||||
fi
|
||||
|
||||
# Per-app pre-migrate hook (optional) — declared in the app's tools.sh.
|
||||
migrateRunHook "$app" pre "$source_host" "restic"
|
||||
|
||||
# ---- 3. The actual restore (reuses existing restoreAppStart) --------------
|
||||
# restoreAppStart already wipes the app folder, restores the snapshot,
|
||||
# re-runs the install-time tag pipeline, and starts the container. The
|
||||
@ -109,6 +112,9 @@ migrateApplyApp()
|
||||
fi
|
||||
fi
|
||||
|
||||
# Per-app post-migrate hook (optional) — last thing before we declare done.
|
||||
migrateRunHook "$app" post "$source_host" "restic"
|
||||
|
||||
local finished_at
|
||||
finished_at=$(date +%s)
|
||||
local duration=$((finished_at - started_at))
|
||||
|
||||
37
scripts/migrate/migrate_hooks.sh
Normal file
37
scripts/migrate/migrate_hooks.sh
Normal file
@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Per-app migrate hooks. After a migrate (restic-mediated apply OR direct-SSH
|
||||
# pull) places the source's data on this host, some apps need app-specific
|
||||
# fix-ups beyond the standard URL rewrite — rotating a federation key,
|
||||
# regenerating an OIDC client secret, dropping a SaaS lock, etc.
|
||||
#
|
||||
# Convention: an app's tools.sh (auto-sourced by the modular per-app tools
|
||||
# loader — see [[libreportal-modular-app-tools]]) may declare:
|
||||
#
|
||||
# <app>_migrate_pre() # called before stop+wipe
|
||||
# <app>_migrate_post() # called after restart, before user sees it
|
||||
#
|
||||
# Both receive: $1 = source_hostname (peer hostname or backup tag),
|
||||
# $2 = transport ("restic" | "direct-ssh")
|
||||
# Hooks are optional — apps without them just inherit the standard flow.
|
||||
|
||||
# Run a single named hook if it exists. Quiet if not defined.
|
||||
migrateRunHook()
|
||||
{
|
||||
local app="$1"
|
||||
local stage="$2" # "pre" or "post"
|
||||
local source="$3"
|
||||
local transport="$4"
|
||||
|
||||
local hook_name="${app}_migrate_${stage}"
|
||||
if declare -f "$hook_name" >/dev/null 2>&1; then
|
||||
isNotice "Running ${stage}-migrate hook for ${app}"
|
||||
migrateEmit phase="hook-${stage}" status=running app="$app" hook="$hook_name"
|
||||
if "$hook_name" "$source" "$transport"; then
|
||||
migrateEmit phase="hook-${stage}" status=complete app="$app"
|
||||
else
|
||||
isNotice "Hook ${hook_name} returned non-zero — continuing migrate"
|
||||
migrateEmit phase="hook-${stage}" status=failed app="$app"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@ -64,6 +64,9 @@ peerPullApp()
|
||||
migrateEmit phase=pre-backup status=skipped reason=user-opt-out app="$app"
|
||||
fi
|
||||
|
||||
# Per-app pre-migrate hook (optional) — declared in the app's tools.sh.
|
||||
migrateRunHook "$app" pre "$peer_name" "direct-ssh"
|
||||
|
||||
# ---- 3. Stop + wipe ----------------------------------------------------
|
||||
migrateEmit phase=stop status=running app="$app"
|
||||
if declare -f dockerComposeDown >/dev/null 2>&1 && [[ -d "$containers_dir$app" ]]; then
|
||||
@ -117,6 +120,9 @@ peerPullApp()
|
||||
fi
|
||||
migrateEmit phase=start-app status=complete app="$app"
|
||||
|
||||
# Per-app post-migrate hook (optional) — last thing before declaring done.
|
||||
migrateRunHook "$app" post "$peer_name" "direct-ssh"
|
||||
|
||||
local finished_at; finished_at=$(date +%s)
|
||||
local duration=$((finished_at - started_at))
|
||||
isSuccessful "Pulled $app from $peer_name in ${duration}s"
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
migrate_scripts=(
|
||||
"migrate/migrate_apply.sh"
|
||||
"migrate/migrate_discover.sh"
|
||||
"migrate/migrate_hooks.sh"
|
||||
"migrate/migrate_pre_backup.sh"
|
||||
"migrate/migrate_preflight.sh"
|
||||
"migrate/migrate_progress.sh"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user