LibrePortal/scripts/restore/restore_app_start.sh
librelad 94c9e83c42 feat(backup): container-side capture of private app files
Reads files the backup user can't see from the host (container-owned, e.g.
Nextcloud's www-data data dir) by streaming them out THROUGH the container
(docker exec tar) — no host root, no host read perms, works rooted + rootless.
Extracts to staging as plain files so restic keeps full dedup + per-file
restore (not a piped tar blob); the live path is excluded from the snapshot.
Restore streams the staging copy back through a throwaway in-namespace
container that recreates the tree with the app's uid:gid.

Declared via a libreportal.backup.files compose label; Nextcloud (html, 33:33)
is the first to use it. Live capture failure falls back to stop-snapshot-start.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 18:15:53 +01:00

142 lines
4.0 KiB
Bash

#!/bin/bash
restoreAppStart()
{
local app_name="$1"
local snapshot_arg="$2"
local location_idx="$3"
local host_filter="$4"
local stored_app_name="$app_name"
if [[ -z "$app_name" ]]; then
isError "restoreAppStart called with empty app_name"
return 1
fi
if [[ -z "$(resticEnabledLocations)" ]]; then
isError "No backup locations enabled — cannot restore"
return 1
fi
isHeader "Restoring $stored_app_name"
local restore_started_at
restore_started_at=$(date -Iseconds)
isNotice "Task started: restore $stored_app_name at $restore_started_at"
((menu_number++))
echo ""
echo "---- $menu_number. Picking backup"
echo ""
local pick
pick=$(restorePickSnapshot "$stored_app_name" "$location_idx" "$snapshot_arg" "$host_filter")
if [[ -z "$pick" ]]; then
isError "No backup to restore from"
return 1
fi
local chosen_idx="${pick%%:*}"
local chosen_id="${pick##*:}"
isSuccessful "Using backup ${chosen_id:0:8} from $(resticLocationName "$chosen_idx")"
((menu_number++))
echo ""
echo "---- $menu_number. Setting up install folder and config for $stored_app_name"
echo ""
dockerConfigSetupToContainer "loud" "$stored_app_name" "install"
initializeAppVariables "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Shutting down container(s) for restoration"
echo ""
dockerComposeDown "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Wiping existing app folder"
echo ""
if [[ -d "$containers_dir$stored_app_name" ]]; then
sudo rm -rf "${containers_dir:?}$stored_app_name"
fi
((menu_number++))
echo ""
echo "---- $menu_number. Running pre-restore hook (if present)"
echo ""
restoreAppRunHook "$stored_app_name" pre
((menu_number++))
echo ""
echo "---- $menu_number. Restoring snapshot ${chosen_id:0:8}"
echo ""
local include_path="$containers_dir$stored_app_name"
engineRestoreSnapshot "$chosen_idx" "$chosen_id" "/" "$include_path"
if [[ $? -ne 0 ]]; then
isError "Restore failed — leaving app in stopped state"
return 1
fi
sudo chown -R "$docker_install_user":"$docker_install_user" "$containers_dir$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Rehydrating databases + files (pre-start)"
echo ""
restoreDbRehydratePreStart "$stored_app_name"
restoreFilesRehydratePreStart "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Updating docker compose file(s)"
echo ""
dockerComposeUpdateAndStartApp "$stored_app_name" install
((menu_number++))
echo ""
echo "---- $menu_number. Fixing permissions before starting"
echo ""
fixPermissionsBeforeStart "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Starting up the $stored_app_name docker service(s)"
echo ""
dockerComposeUp "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Running post-restore hook (if present)"
echo ""
restoreAppRunHook "$stored_app_name" post
((menu_number++))
echo ""
echo "---- $menu_number. Loading database dumps (post-start)"
echo ""
restoreDbReplayPostStart "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Logging restore into database"
echo ""
databaseRestoreInsert "$stored_app_name"
databaseInstallApp "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Running Headscale setup (if required)"
echo ""
setupHeadscale "$stored_app_name"
((menu_number++))
echo ""
echo "---- $menu_number. Running app-specific updates (if required)"
echo ""
appUpdateSpecifics "$stored_app_name"
local restore_finished_at
restore_finished_at=$(date -Iseconds)
isSuccessful "Task finished: restore $stored_app_name at $restore_finished_at (started $restore_started_at)"
menu_number=0
}