diff --git a/scripts/backup/app/backup_app_start.sh b/scripts/backup/app/backup_app_start.sh index 819b81b..6f4fa87 100755 --- a/scripts/backup/app/backup_app_start.sh +++ b/scripts/backup/app/backup_app_start.sh @@ -43,7 +43,7 @@ backupAppStart() isNotice "Live strategy — containers stay running; databases dumped + private files captured via their containers" if ! backupDbDump "$stored_app_name" || ! backupFilesCapture "$stored_app_name"; then 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" dockerComposeDown "$stored_app_name" fi diff --git a/scripts/backup/db/backup_db.sh b/scripts/backup/db/backup_db.sh index 9b408c2..089dfa3 100644 --- a/scripts/backup/db/backup_db.sh +++ b/scripts/backup/db/backup_db.sh @@ -170,10 +170,10 @@ _backupDbImport() local kind="$1" container="$2" dump="$3" case "$kind" in 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 ;; *) - 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 ;; esac } @@ -189,7 +189,7 @@ backupDbDump() backupDbHasDescriptors "$app" || return 0 - sudo mkdir -p "$dump_dir" + runFileOp mkdir -p "$dump_dir" while IFS= read -r desc; do [[ -z "$desc" ]] && continue @@ -201,7 +201,7 @@ backupDbDump() isNotice "Dumping postgres ($container) — live, consistent" 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}}"' \ - 2>/dev/null | gzip | sudo tee "$dump" >/dev/null; then + 2>/dev/null | gzip | runFileWrite "$dump"; then isSuccessful "postgres dump written ($container)" else isError "postgres dump failed ($container)"; rc=1 @@ -211,7 +211,7 @@ backupDbDump() isNotice "Dumping $kind ($container) — live, consistent" 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")' \ - 2>/dev/null | gzip | sudo tee "$dump" >/dev/null; then + 2>/dev/null | gzip | runFileWrite "$dump"; then isSuccessful "$kind dump written ($container)" else isError "$kind dump failed ($container)"; rc=1 @@ -231,11 +231,11 @@ backupDbDump() fi # .backup takes a consistent copy even while the app writes. 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 - sudo rm -f "$tmp" + if runFileOp sqlite3 "$src" ".backup '$tmp'" 2>/dev/null && runFileOp gzip -c "$tmp" | runFileWrite "$dump"; then + runFileOp rm -f "$tmp" isSuccessful "sqlite dump written ($path)" else - sudo rm -f "$tmp" + runFileOp rm -f "$tmp" isError "sqlite dump failed ($path)"; rc=1 fi ;; @@ -244,7 +244,7 @@ backupDbDump() esac 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 } @@ -296,15 +296,15 @@ restoreDbRehydratePreStart() case "$kind" in sqlite) [[ -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" - sudo mkdir -p "$(dirname "$app_dir/$path")" - sudo gzip -dc "$dump" | sudo tee "$app_dir/$path" >/dev/null - sudo chown -R "$docker_install_user":"$docker_install_user" "$(dirname "$app_dir/$path")" + runFileOp rm -f "$app_dir/$path" "$app_dir/$path-wal" "$app_dir/$path-shm" + runFileOp mkdir -p "$(dirname "$app_dir/$path")" + runFileOp gzip -dc "$dump" | runFileWrite "$app_dir/$path" + runFileOp chown -R "$docker_install_user":"$docker_install_user" "$(dirname "$app_dir/$path")" isSuccessful "sqlite $path rehydrated from dump" ;; *) [[ -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" ;; esac diff --git a/scripts/backup/files/backup_files.sh b/scripts/backup/files/backup_files.sh index 4a377f9..7cd9fb7 100644 --- a/scripts/backup/files/backup_files.sh +++ b/scripts/backup/files/backup_files.sh @@ -74,17 +74,16 @@ backupFilesCapture() stage="$app_dir/$backup_files_stage_subdir/$subdir" isNotice "Capturing $subdir from $container — live, via container" - rm -rf "$stage" 2>/dev/null - mkdir -p "$stage" + runFileOp rm -rf "$stage" 2>/dev/null + runFileOp mkdir -p "$stage" # 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), # 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 # owner can now read everything. Real ownership is reapplied from the # descriptor on restore. - 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 + runFileOp chown -R "$docker_install_user":"$docker_install_user" "$stage" 2>/dev/null isSuccessful "captured $subdir ($(du -sh "$stage" 2>/dev/null | cut -f1))" else 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 # 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). - 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" \ 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" diff --git a/scripts/restore/restore_app_start.sh b/scripts/restore/restore_app_start.sh index 201889f..8d73b0c 100644 --- a/scripts/restore/restore_app_start.sh +++ b/scripts/restore/restore_app_start.sh @@ -56,7 +56,7 @@ restoreAppStart() echo "---- $menu_number. Wiping existing app folder" echo "" 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 ((menu_number++)) @@ -75,7 +75,7 @@ restoreAppStart() 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" + runFileOp chown -R "$docker_install_user":"$docker_install_user" "$containers_dir$stored_app_name" ((menu_number++)) echo ""