e9c7e9df1a
- tv app/Hobbs-Family-Organizer-Self-Hosted-v1.0.0: release bundle (Next.js app + Shield APK v1.4 + source) - compose/hf-pdocker-02/hobbs-organizer: override (local-disk secrets, watchtower off), .env (port 8340), Shield provisioning script - CLAUDE.md: service tree + internal ports Trusted-LAN mode. Secrets live only on .100 /var/lib/hobbs-organizer/secrets (not in git). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E9NnVEico3WvFxLyjy1iW3
69 lines
2.5 KiB
Bash
69 lines
2.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
umask 077
|
|
|
|
SCRIPT_DIRECTORY="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
PROJECT_ROOT="$(cd -- "$SCRIPT_DIRECTORY/.." && pwd -P)"
|
|
FORCE=0
|
|
|
|
usage() {
|
|
printf '%s\n' "Usage: $0 [--force] BACKUP.tar.gz" >&2
|
|
}
|
|
|
|
if [[ "${1:-}" == "--force" ]]; then
|
|
FORCE=1
|
|
shift
|
|
fi
|
|
[[ $# -eq 1 ]] || { usage; exit 2; }
|
|
|
|
archive_argument="$1"
|
|
archive_directory="$(cd -- "$(dirname -- "$archive_argument")" && pwd -P)"
|
|
archive_path="$archive_directory/$(basename -- "$archive_argument")"
|
|
[[ -f "$archive_path" ]] || { printf '%s\n' "The backup archive does not exist." >&2; exit 1; }
|
|
command -v tar >/dev/null 2>&1 || { printf '%s\n' "tar is required to restore a backup." >&2; exit 1; }
|
|
|
|
if ((FORCE == 0)); then
|
|
read -r -p "Restore will overwrite the current .env and runtime secrets. Continue? [y/N] " restore_answer
|
|
case "${restore_answer,,}" in
|
|
y|yes) ;;
|
|
*) printf '%s\n' "Restore cancelled." >&2; exit 1 ;;
|
|
esac
|
|
fi
|
|
|
|
while IFS= read -r archive_entry; do
|
|
normalized_entry="${archive_entry%/}"
|
|
case "$normalized_entry" in
|
|
config|config/environment.env|secrets|secrets/calendar_ical_urls.json|secrets/organizer_access_token.txt|manifest.txt) ;;
|
|
*) printf '%s\n' "The archive contains an unexpected path and will not be restored." >&2; exit 1 ;;
|
|
esac
|
|
done < <(tar -tzf "$archive_path")
|
|
|
|
staging_path="$(mktemp -d "$PROJECT_ROOT/.restore-staging-XXXXXXXX")"
|
|
cleanup_staging() {
|
|
[[ -n "${staging_path:-}" && -d "$staging_path" ]] && rm -rf -- "$staging_path"
|
|
}
|
|
trap cleanup_staging EXIT HUP INT TERM
|
|
|
|
tar -xzf "$archive_path" -C "$staging_path"
|
|
for required_file in \
|
|
"$staging_path/config/environment.env" \
|
|
"$staging_path/secrets/calendar_ical_urls.json" \
|
|
"$staging_path/secrets/organizer_access_token.txt"; do
|
|
[[ -f "$required_file" && ! -L "$required_file" ]] || {
|
|
printf '%s\n' "The archive is missing a required regular file." >&2
|
|
exit 1
|
|
}
|
|
done
|
|
|
|
mkdir -p -- "$PROJECT_ROOT/secrets"
|
|
cp -- "$staging_path/config/environment.env" "$PROJECT_ROOT/.env"
|
|
cp -- "$staging_path/secrets/calendar_ical_urls.json" "$PROJECT_ROOT/secrets/calendar_ical_urls.json"
|
|
cp -- "$staging_path/secrets/organizer_access_token.txt" "$PROJECT_ROOT/secrets/organizer_access_token.txt"
|
|
chmod 600 -- "$PROJECT_ROOT/.env" "$PROJECT_ROOT/secrets/calendar_ical_urls.json" "$PROJECT_ROOT/secrets/organizer_access_token.txt"
|
|
|
|
rm -rf -- "$staging_path"
|
|
staging_path=""
|
|
trap - EXIT HUP INT TERM
|
|
|
|
printf '%s\n' "Configuration restored. Restart with: docker compose up -d --build"
|