dockerDeleteData (uninstall) and the wipe-before-restore step in
restoreAppStart both did `runFileOp rm -rf $containers_dir$app_name`,
which runs as $CFG_DOCKER_INSTALL_USER (dockerinstall, uid 1002 on
rootless). That user owns app-template files but CANNOT remove
container sub-UID dirs created by the daemon's userns mapping —
postgres data at uid 232070, nextcloud html at uid 33, etc. The rm
therefore silently failed with
rm: cannot remove '/libreportal-containers/invidious/postgresdata':
Permission denied
while still reporting "<app> successfully uninstalled" — leaving the
sub-UID directory tree on disk to confuse the next install and leak
storage.
Fix: route the wipe through a new `app-data-remove` action in the
root-owned libreportal-ownership helper. Root can rm sub-UID files
unconditionally. The helper validates the app name (alphanumeric +
. _ -, no traversal), refuses the WebUI's own slot (libreportal), and
is idempotent when the dir is already gone.
Two callers updated:
- scripts/docker/app/uninstall/delete_data.sh
- scripts/restore/restore_app_start.sh
The helper itself ships root-owned at /usr/local/lib/libreportal/, so a
fresh install or release upgrade is needed to pick up the new action.
Bumped init.sh footprint_version 2 → 3 so the runtime updater
prompts a root re-install on the next release.
Signed-off-by: librelad <librelad@digitalangels.vip>
145 lines
4.2 KiB
Bash
145 lines
4.2 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
|
|
# Root-owned helper, not runFileOp — restoring over an app that left
|
|
# sub-UID data behind (postgres, www-data, …) needs to actually wipe
|
|
# those dirs before laying the snapshot down.
|
|
runOwnership app-data-remove "$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
|
|
runFileOp 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
|
|
}
|