feat(hobbs-organizer): Shield photo app (com.dayline.photos) + monitoring, links, .100 boot fix

- shield/photos-source: launcher tile + DreamService screensaver showing /photo
  (locked-down WebView, default URL baked in, files/base_url override)
- build-photos-apk.sh: containerized build on hf-pdocker-02 (JDK17, SDK 35);
  signing key kept off-repo on .100 + NAS
- Hobbs-Family-Photos-1.0.apk installed on .186 (canary) and set as its screensaver
- Prometheus probes for /api/health and /api/health/photos; Farm Manager links + group
- CLAUDE.md: .100 left the stale Swarm and its NFS mount waits for network (11-03 outage)

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 19:28:12 -06:00
parent c2e5c1d9dc
commit 0a3ec27774
20 changed files with 501 additions and 1 deletions
@@ -1,5 +1,11 @@
# Hobbs Family Organizer (Shield TV dashboard) — hf-pdocker-02
**Shield photo app (2026-11-04): `com.dayline.photos` v1.0.** A launcher tile plus a screensaver, both showing `/photo`.
- Build: `sudo ./build-photos-apk.sh`. It writes `~/hobbs-shield/Hobbs-Family-Photos-<ver>.apk` on .100.
- Install: `adb install -r <apk>`.
- Set the screensaver: `adb shell settings put secure screensaver_components com.dayline.photos/.PhotoDreamService`.
- Signing key: `/var/lib/hobbs-organizer/apk-signing/` plus a NAS copy. Keep it; every update must use it.
**v1.2.0 (2026-11-04): photo frame at http://192.168.86.100:8340/photo** — Google Photos shared albums
(admin → Photos tab: add share link, Check, Sync now; display interval/fit/shuffle/date) + uploads (resized in the browser).
Cache: `/var/lib/hobbs-organizer/data/photos/<album-id>/` (+ `uploads/`), re-synced every 6h. Stale check: `/api/health/photos` (503 after 48h).
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Builds the Hobbs Family Photos Shield APK (com.dayline.photos) on hf-pdocker-02.
# Usage: sudo ./build-photos-apk.sh -> ~/hobbs-shield/Hobbs-Family-Photos-<ver>.apk
# The signing key is created on first run in /var/lib/hobbs-organizer/apk-signing (root 600)
# and copied to the NAS. NEVER delete it: updates must be signed with the same key.
set -euo pipefail
DEPLOY=/mnt/docker-data/compose/hf-pdocker-02/hobbs-organizer
SRC="$DEPLOY/shield/photos-source"
SIGN=/var/lib/hobbs-organizer/apk-signing
NAS_SIGN=/mnt/docker-data/configs/hobbs-organizer/apk-signing
WORK=/var/tmp/hfo-photos-build
OUT=/home/glyptic/hobbs-shield
IMAGE=eclipse-temurin:17-jdk-jammy
[ "$(id -u)" = 0 ] || { echo "run with sudo"; exit 1; }
install -d -m 700 "$SIGN"
if [ ! -f "$SIGN/hobbs-photos.jks" ]; then
echo "== creating signing key"
PASS=$(openssl rand -base64 32 | tr -d '/+=' | cut -c1-32)
docker run --rm -v "$SIGN:/signing" "$IMAGE" keytool -genkeypair -keystore /signing/hobbs-photos.jks \
-storetype PKCS12 -alias hobbs-photos -keyalg RSA -keysize 4096 -validity 20000 \
-dname "CN=Hobbs Family Photos, O=Hobbs Farm" -storepass "$PASS" -keypass "$PASS" >/dev/null
printf 'storeFile=/signing/hobbs-photos.jks\nstorePassword=%s\nkeyAlias=hobbs-photos\nkeyPassword=%s\n' "$PASS" "$PASS" > "$SIGN/keystore.properties"
chmod 600 "$SIGN"/*
fi
# Off-box copy (NAS root_squash: write as glyptic, keep it private).
sudo -u glyptic install -d -m 700 "$NAS_SIGN"
for f in hobbs-photos.jks keystore.properties; do
cat "$SIGN/$f" | sudo -u glyptic sh -c "umask 077; cat > '$NAS_SIGN/$f'"
done
rm -rf "$WORK"; mkdir -p "$WORK"; cp -a "$SRC/." "$WORK/"
cp "$SIGN/keystore.properties" "$WORK/keystore.properties"
docker volume create hobbs-android-sdk >/dev/null
docker run --rm -v "$WORK:/src" -v hobbs-android-sdk:/opt/cache -v "$SIGN:/signing:ro" -w /src "$IMAGE" bash -euo pipefail -c '
export ANDROID_HOME=/opt/cache/sdk GRADLE_USER_HOME=/opt/cache/gradle-home
command -v unzip >/dev/null || { apt-get update -qq && apt-get install -y -qq unzip curl >/dev/null; }
if [ ! -x $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager ]; then
echo "== downloading Android command-line tools"
curl -fsSL -o /tmp/clt.zip https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
mkdir -p $ANDROID_HOME/cmdline-tools && unzip -q /tmp/clt.zip -d /tmp/clt && mv /tmp/clt/cmdline-tools $ANDROID_HOME/cmdline-tools/latest
fi
if [ ! -d $ANDROID_HOME/platforms/android-35 ] || [ ! -d $ANDROID_HOME/build-tools/35.0.0 ]; then
echo "== installing SDK platform 35 + build-tools 35.0.0"
(yes 2>/dev/null || true) | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses >/dev/null || true
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platforms;android-35" "build-tools;35.0.0" >/dev/null
fi
if [ ! -x /opt/cache/gradle-8.9/bin/gradle ]; then
echo "== downloading Gradle 8.9"
curl -fsSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-8.9-bin.zip
unzip -q /tmp/gradle.zip -d /opt/cache
fi
echo "sdk.dir=$ANDROID_HOME" > local.properties
/opt/cache/gradle-8.9/bin/gradle --no-daemon -q assembleRelease
$ANDROID_HOME/build-tools/35.0.0/apksigner verify --print-certs app/build/outputs/apk/release/app-release.apk | grep -E "Signer #1 certificate (DN|SHA-256)"
$ANDROID_HOME/build-tools/35.0.0/aapt2 dump badging app/build/outputs/apk/release/app-release.apk | grep -E "^package:|application-debuggable|leanback"
'
VER=$(grep -oP 'versionName "\K[^"]+' "$SRC/app/build.gradle")
install -d -o glyptic -g glyptic "$OUT"
install -o glyptic -g glyptic -m 644 "$WORK/app/build/outputs/apk/release/app-release.apk" "$OUT/Hobbs-Family-Photos-$VER.apk"
sha256sum "$OUT/Hobbs-Family-Photos-$VER.apk"
rm -rf "$WORK"