feat: self-host Hobbs Family Organizer (Shield TV dashboard) on hf-pdocker-02:8340

- 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
This commit is contained in:
2026-11-04 18:27:22 -06:00
parent 122ea392d1
commit e9c7e9df1a
68 changed files with 12831 additions and 0 deletions
@@ -0,0 +1,68 @@
#!/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"