refactor(backup): route /docker ops through mode-aware helper
Convert the backup/restore data-plane sudo calls (mkdir/chown/rm/sqlite3/tar/ gzip|tee) to runFileOp/runFileWrite. Rooted behaviour is identical (helper runs sudo); rootless will run them as the unprivileged install user. Pilot subsystem for the wider de-sudo. verify.sh's /tmp scratch ops left as-is (different ownership domain, handled separately). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
e89ce25a19
commit
d5acb7e169
@ -43,7 +43,7 @@ backupAppStart()
|
|||||||
isNotice "Live strategy — containers stay running; databases dumped + private files captured via their containers"
|
isNotice "Live strategy — containers stay running; databases dumped + private files captured via their containers"
|
||||||
if ! backupDbDump "$stored_app_name" || ! backupFilesCapture "$stored_app_name"; then
|
if ! backupDbDump "$stored_app_name" || ! backupFilesCapture "$stored_app_name"; then
|
||||||
isError "Live capture failed — falling back to stop-snapshot-start for safety"
|
isError "Live capture failed — falling back to stop-snapshot-start for safety"
|
||||||
sudo rm -rf "${containers_dir:?}$stored_app_name/.lp-backup"
|
runFileOp rm -rf "${containers_dir:?}$stored_app_name/.lp-backup"
|
||||||
strategy="stop-snapshot-start"
|
strategy="stop-snapshot-start"
|
||||||
dockerComposeDown "$stored_app_name"
|
dockerComposeDown "$stored_app_name"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -170,10 +170,10 @@ _backupDbImport()
|
|||||||
local kind="$1" container="$2" dump="$3"
|
local kind="$1" container="$2" dump="$3"
|
||||||
case "$kind" in
|
case "$kind" in
|
||||||
postgres)
|
postgres)
|
||||||
sudo gzip -dc "$dump" | docker exec -i "$container" sh -c \
|
runFileOp gzip -dc "$dump" | docker exec -i "$container" sh -c \
|
||||||
'export PGPASSWORD="${POSTGRES_PASSWORD:-}"; psql -v ON_ERROR_STOP=1 -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-${POSTGRES_USER:-postgres}}"' >/dev/null 2>&1 ;;
|
'export PGPASSWORD="${POSTGRES_PASSWORD:-}"; psql -v ON_ERROR_STOP=1 -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-${POSTGRES_USER:-postgres}}"' >/dev/null 2>&1 ;;
|
||||||
*)
|
*)
|
||||||
sudo gzip -dc "$dump" | docker exec -i "$container" sh -c \
|
runFileOp gzip -dc "$dump" | docker exec -i "$container" sh -c \
|
||||||
'RP="${MARIADB_ROOT_PASSWORD:-$MYSQL_ROOT_PASSWORD}"; (mariadb -uroot -p"$RP" 2>/dev/null || mysql -uroot -p"$RP")' >/dev/null 2>&1 ;;
|
'RP="${MARIADB_ROOT_PASSWORD:-$MYSQL_ROOT_PASSWORD}"; (mariadb -uroot -p"$RP" 2>/dev/null || mysql -uroot -p"$RP")' >/dev/null 2>&1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@ -189,7 +189,7 @@ backupDbDump()
|
|||||||
|
|
||||||
backupDbHasDescriptors "$app" || return 0
|
backupDbHasDescriptors "$app" || return 0
|
||||||
|
|
||||||
sudo mkdir -p "$dump_dir"
|
runFileOp mkdir -p "$dump_dir"
|
||||||
|
|
||||||
while IFS= read -r desc; do
|
while IFS= read -r desc; do
|
||||||
[[ -z "$desc" ]] && continue
|
[[ -z "$desc" ]] && continue
|
||||||
@ -201,7 +201,7 @@ backupDbDump()
|
|||||||
isNotice "Dumping postgres ($container) — live, consistent"
|
isNotice "Dumping postgres ($container) — live, consistent"
|
||||||
if docker exec "$container" sh -c \
|
if docker exec "$container" sh -c \
|
||||||
'export PGPASSWORD="${POSTGRES_PASSWORD:-}"; pg_dump --clean --if-exists -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-${POSTGRES_USER:-postgres}}"' \
|
'export PGPASSWORD="${POSTGRES_PASSWORD:-}"; pg_dump --clean --if-exists -U "${POSTGRES_USER:-postgres}" -d "${POSTGRES_DB:-${POSTGRES_USER:-postgres}}"' \
|
||||||
2>/dev/null | gzip | sudo tee "$dump" >/dev/null; then
|
2>/dev/null | gzip | runFileWrite "$dump"; then
|
||||||
isSuccessful "postgres dump written ($container)"
|
isSuccessful "postgres dump written ($container)"
|
||||||
else
|
else
|
||||||
isError "postgres dump failed ($container)"; rc=1
|
isError "postgres dump failed ($container)"; rc=1
|
||||||
@ -211,7 +211,7 @@ backupDbDump()
|
|||||||
isNotice "Dumping $kind ($container) — live, consistent"
|
isNotice "Dumping $kind ($container) — live, consistent"
|
||||||
if docker exec "$container" sh -c \
|
if docker exec "$container" sh -c \
|
||||||
'RP="${MARIADB_ROOT_PASSWORD:-$MYSQL_ROOT_PASSWORD}"; DB="${MARIADB_DATABASE:-$MYSQL_DATABASE}"; (mariadb-dump -uroot -p"$RP" --single-transaction --routines --triggers --databases "$DB" 2>/dev/null || mysqldump -uroot -p"$RP" --single-transaction --routines --triggers --databases "$DB")' \
|
'RP="${MARIADB_ROOT_PASSWORD:-$MYSQL_ROOT_PASSWORD}"; DB="${MARIADB_DATABASE:-$MYSQL_DATABASE}"; (mariadb-dump -uroot -p"$RP" --single-transaction --routines --triggers --databases "$DB" 2>/dev/null || mysqldump -uroot -p"$RP" --single-transaction --routines --triggers --databases "$DB")' \
|
||||||
2>/dev/null | gzip | sudo tee "$dump" >/dev/null; then
|
2>/dev/null | gzip | runFileWrite "$dump"; then
|
||||||
isSuccessful "$kind dump written ($container)"
|
isSuccessful "$kind dump written ($container)"
|
||||||
else
|
else
|
||||||
isError "$kind dump failed ($container)"; rc=1
|
isError "$kind dump failed ($container)"; rc=1
|
||||||
@ -231,11 +231,11 @@ backupDbDump()
|
|||||||
fi
|
fi
|
||||||
# .backup takes a consistent copy even while the app writes.
|
# .backup takes a consistent copy even while the app writes.
|
||||||
local tmp="$dump_dir/.$(basename "$path").tmp"
|
local tmp="$dump_dir/.$(basename "$path").tmp"
|
||||||
if sudo sqlite3 "$src" ".backup '$tmp'" 2>/dev/null && sudo gzip -c "$tmp" | sudo tee "$dump" >/dev/null; then
|
if runFileOp sqlite3 "$src" ".backup '$tmp'" 2>/dev/null && runFileOp gzip -c "$tmp" | runFileWrite "$dump"; then
|
||||||
sudo rm -f "$tmp"
|
runFileOp rm -f "$tmp"
|
||||||
isSuccessful "sqlite dump written ($path)"
|
isSuccessful "sqlite dump written ($path)"
|
||||||
else
|
else
|
||||||
sudo rm -f "$tmp"
|
runFileOp rm -f "$tmp"
|
||||||
isError "sqlite dump failed ($path)"; rc=1
|
isError "sqlite dump failed ($path)"; rc=1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@ -244,7 +244,7 @@ backupDbDump()
|
|||||||
esac
|
esac
|
||||||
done < <(backupDbDescriptors "$app")
|
done < <(backupDbDescriptors "$app")
|
||||||
|
|
||||||
sudo chown -R "$docker_install_user":"$docker_install_user" "$dump_dir" 2>/dev/null
|
runFileOp chown -R "$docker_install_user":"$docker_install_user" "$dump_dir" 2>/dev/null
|
||||||
return $rc
|
return $rc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,15 +296,15 @@ restoreDbRehydratePreStart()
|
|||||||
case "$kind" in
|
case "$kind" in
|
||||||
sqlite)
|
sqlite)
|
||||||
[[ -f "$dump" ]] || { isNotice "No sqlite dump for $path — leaving app to initialise"; continue; }
|
[[ -f "$dump" ]] || { isNotice "No sqlite dump for $path — leaving app to initialise"; continue; }
|
||||||
sudo rm -f "$app_dir/$path" "$app_dir/$path-wal" "$app_dir/$path-shm"
|
runFileOp rm -f "$app_dir/$path" "$app_dir/$path-wal" "$app_dir/$path-shm"
|
||||||
sudo mkdir -p "$(dirname "$app_dir/$path")"
|
runFileOp mkdir -p "$(dirname "$app_dir/$path")"
|
||||||
sudo gzip -dc "$dump" | sudo tee "$app_dir/$path" >/dev/null
|
runFileOp gzip -dc "$dump" | runFileWrite "$app_dir/$path"
|
||||||
sudo chown -R "$docker_install_user":"$docker_install_user" "$(dirname "$app_dir/$path")"
|
runFileOp chown -R "$docker_install_user":"$docker_install_user" "$(dirname "$app_dir/$path")"
|
||||||
isSuccessful "sqlite $path rehydrated from dump"
|
isSuccessful "sqlite $path rehydrated from dump"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
[[ -f "$dump" ]] || { isNotice "No dump for $container — keeping restored data dir as-is"; continue; }
|
[[ -f "$dump" ]] || { isNotice "No dump for $container — keeping restored data dir as-is"; continue; }
|
||||||
[[ -n "$datadir" ]] && sudo rm -rf "${app_dir:?}/$datadir"
|
[[ -n "$datadir" ]] && runFileOp rm -rf "${app_dir:?}/$datadir"
|
||||||
isNotice "Cleared $datadir — $container will init fresh, then load the dump"
|
isNotice "Cleared $datadir — $container will init fresh, then load the dump"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@ -74,17 +74,16 @@ backupFilesCapture()
|
|||||||
stage="$app_dir/$backup_files_stage_subdir/$subdir"
|
stage="$app_dir/$backup_files_stage_subdir/$subdir"
|
||||||
|
|
||||||
isNotice "Capturing $subdir from $container — live, via container"
|
isNotice "Capturing $subdir from $container — live, via container"
|
||||||
rm -rf "$stage" 2>/dev/null
|
runFileOp rm -rf "$stage" 2>/dev/null
|
||||||
mkdir -p "$stage"
|
runFileOp mkdir -p "$stage"
|
||||||
# Read in the container's namespace, write the plain tree to staging.
|
# Read in the container's namespace, write the plain tree to staging.
|
||||||
if docker exec "$container" tar -C "$cpath" -cf - . 2>/dev/null | tar -xf - -C "$stage" 2>/dev/null; then
|
if docker exec "$container" tar -C "$cpath" -cf - . 2>/dev/null | runFileOp tar -xf - -C "$stage" 2>/dev/null; then
|
||||||
# The capture preserves the app's ownership (e.g. www-data, 0640),
|
# The capture preserves the app's ownership (e.g. www-data, 0640),
|
||||||
# which the backup user still couldn't read. Hand the staging tree to
|
# which the backup user still couldn't read. Hand the staging tree to
|
||||||
# the backup user so restic can read it; modes are unchanged, so the
|
# the backup user so restic can read it; modes are unchanged, so the
|
||||||
# owner can now read everything. Real ownership is reapplied from the
|
# owner can now read everything. Real ownership is reapplied from the
|
||||||
# descriptor on restore.
|
# descriptor on restore.
|
||||||
chown -R "$docker_install_user":"$docker_install_user" "$stage" 2>/dev/null \
|
runFileOp chown -R "$docker_install_user":"$docker_install_user" "$stage" 2>/dev/null
|
||||||
|| sudo chown -R "$docker_install_user":"$docker_install_user" "$stage" 2>/dev/null
|
|
||||||
isSuccessful "captured $subdir ($(du -sh "$stage" 2>/dev/null | cut -f1))"
|
isSuccessful "captured $subdir ($(du -sh "$stage" 2>/dev/null | cut -f1))"
|
||||||
else
|
else
|
||||||
isError "capture of $subdir from $container failed"
|
isError "capture of $subdir from $container failed"
|
||||||
@ -132,7 +131,7 @@ restoreFilesRehydratePreStart()
|
|||||||
# Helper runs as in-namespace root: it can clear/create the dir under the
|
# Helper runs as in-namespace root: it can clear/create the dir under the
|
||||||
# app dir, extract the streamed tree, and chown to the app's uid:gid
|
# app dir, extract the streamed tree, and chown to the app's uid:gid
|
||||||
# (which maps to the right owner in rooted and rootless alike).
|
# (which maps to the right owner in rooted and rootless alike).
|
||||||
if tar -C "$stage" -cf - . 2>/dev/null | docker run --rm -i \
|
if runFileOp tar -C "$stage" -cf - . 2>/dev/null | docker run --rm -i \
|
||||||
-v "$app_dir:/parent" "$backup_files_helper_image" \
|
-v "$app_dir:/parent" "$backup_files_helper_image" \
|
||||||
sh -c "rm -rf '/parent/$subdir' && mkdir -p '/parent/$subdir' && tar -C '/parent/$subdir' -xf - && chown -R $uid:$gid '/parent/$subdir'" 2>/dev/null; then
|
sh -c "rm -rf '/parent/$subdir' && mkdir -p '/parent/$subdir' && tar -C '/parent/$subdir' -xf - && chown -R $uid:$gid '/parent/$subdir'" 2>/dev/null; then
|
||||||
isSuccessful "restored $subdir"
|
isSuccessful "restored $subdir"
|
||||||
|
|||||||
@ -56,7 +56,7 @@ restoreAppStart()
|
|||||||
echo "---- $menu_number. Wiping existing app folder"
|
echo "---- $menu_number. Wiping existing app folder"
|
||||||
echo ""
|
echo ""
|
||||||
if [[ -d "$containers_dir$stored_app_name" ]]; then
|
if [[ -d "$containers_dir$stored_app_name" ]]; then
|
||||||
sudo rm -rf "${containers_dir:?}$stored_app_name"
|
runFileOp rm -rf "${containers_dir:?}$stored_app_name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
@ -75,7 +75,7 @@ restoreAppStart()
|
|||||||
isError "Restore failed — leaving app in stopped state"
|
isError "Restore failed — leaving app in stopped state"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
sudo chown -R "$docker_install_user":"$docker_install_user" "$containers_dir$stored_app_name"
|
runFileOp chown -R "$docker_install_user":"$docker_install_user" "$containers_dir$stored_app_name"
|
||||||
|
|
||||||
((menu_number++))
|
((menu_number++))
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user