LibrePortal/scripts/migrate/migrate_hooks.sh
librelad 82f64eb5c0 feat(migrate): app-specific hooks + peer friendly-name overlay (Phase 4)
Polish pass for the migration system. Two concrete additions; the live-mirror
and full drift-verify ideas from the original plan are intentionally
deferred — both need real-world test data to land correctly, and the kernel
already exposes everything they'd need.

Per-app migrate hooks (scripts/migrate/migrate_hooks.sh):
  Apps can declare two optional functions in their tools.sh (already
  auto-sourced per [[libreportal-modular-app-tools]]):

    <app>_migrate_pre()   — runs before stop+wipe
    <app>_migrate_post()  — runs after restart, before the user sees it

  Each receives:
    $1 = source identifier (peer name or backup-tag hostname)
    $2 = transport ("restic" | "direct-ssh")

  migrateRunHook() is now called from both migration apply paths:
    - migrate_apply.sh (restic-mediated, shared backup channel)
    - peer_pull.sh    (direct-SSH, peer-shell stream)

  Use cases: rotate federation keys after a Mastodon move, regenerate
  OIDC client secrets, drop SaaS-style locks, fix hostname-baked configs
  the URL-rewrite layer doesn't cover.

  Hooks are optional — apps without them inherit the standard flow.
  Failed hooks emit a non-fatal notice (the rest of the migrate still
  reaches 'done') so a single bad hook can't strand an otherwise-working
  app in stopped state.

Peer friendly-name overlay (Migrate tab):
  Was deferred from Phase 2 because it required Phase 3's UI to feel
  cohesive. BackupPage.refreshAll() now also fetches peers.json and builds
  a hostname → peer-name lookup. renderMigrate() shows
      'homelab (host: homelab.lan)'
  for any backup-channel peer that matches the source host, and falls back
  to the bare hostname when no peer is defined. Same data, friendlier UI.

Skipped (genuinely deferred, not just out of time):
  - Live mirror / warm-standby (continuous one-way sync). Needs a scheduler
    + drift-state to track. Right place for it is a separate feature on top
    of the existing kernel rather than bolted onto migrate.
  - Drift-verify ("what would change if I migrated?"). Cheap to write but
    needs a real cross-host pair to validate against — adding it untested
    would just be theatre.

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-26 18:00:26 +01:00

38 lines
1.5 KiB
Bash

#!/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
}