Files
hobbs-iac/tv app/Hobbs-Family-Organizer-Self-Hosted-v1.0.0/README.md
T
jeremy e9c7e9df1a 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
2026-11-04 18:27:22 -06:00

9.4 KiB

Hobbs' Family Organizer App — self-hosted package

This release moves the organizer off ChatGPT Sites and onto your own container host. The dashboard, API routes, calendar parser, weather, FRED market ticker, RSS headlines, QR handoff, and Shield wrapper are included.

The local deployment does not require ChatGPT, an OpenAI account, or an OpenAI API key. It is stateless and has no database or persistent application volume.

What still uses the Internet

The container needs outbound DNS and HTTPS for:

  • calendar.google.com — private Google Calendar ICS feeds
  • api.weather.gov — seven-day National Weather Service forecast
  • fred.stlouisfed.org — public FRED series pages used by the ticker
  • the configured OpenAI, Google AI, DeepMind, NVIDIA, Ars Technica, and Wired RSS feeds

No inbound Internet access is required. Keep the service on your LAN, VPN, or behind your existing reverse proxy.

Package contents

  • Dockerfile — production Node 22 image
  • compose.yaml — hardened Compose deployment on port 8080 by default
  • app/ — complete Next.js dashboard and API source
  • container/start.mjs — validates secrets, then drops the app to uid/gid 10001
  • scripts/configure.* — creates runtime secrets without printing them
  • scripts/backup.* and scripts/restore.* — configuration backup and restore
  • secrets/ — ignored runtime secret location and a calendar template
  • shield/ — signed v1.4 APK, provisioning scripts, and Android source
  • MIGRATION.md — ordered cutover and rollback procedure

Requirements

  • A Linux, Windows, or macOS host with Docker Engine/Desktop and Docker Compose v2
  • Outbound HTTPS from the container host
  • One to ten private Google Calendar basic.ics URLs
  • For Shield deployment: Android Platform Tools (adb) and network debugging enabled on the Shield

The image builds for both linux/amd64 and linux/arm64 when the selected Node base image supports the host architecture.

Quick start

Linux or macOS

chmod +x scripts/*.sh
./scripts/configure.sh --mode trusted-lan
docker compose config --quiet
docker compose up -d --build --wait
curl -fsS http://127.0.0.1:8080/api/health

Windows PowerShell

Set-ExecutionPolicy -Scope Process Bypass
.\scripts\configure.ps1 -Mode trusted-lan
docker compose config --quiet
docker compose up -d --build --wait
Invoke-RestMethod http://127.0.0.1:8080/api/health

The configuration script:

  1. copies .env.example to .env if needed;
  2. prompts privately for each Google Calendar ICS URL;
  3. writes secrets/calendar_ical_urls.json with restrictive permissions;
  4. creates secrets/organizer_access_token.txt, empty for trusted-LAN mode or populated with a random token for bearer mode.

After startup, open http://SERVER-IP:8080/ or the HTTPS name provided by your reverse proxy.

Access modes

Trusted LAN

Use this when firewall/VLAN/VPN rules already restrict the dashboard:

./scripts/configure.sh --mode trusted-lan
.\scripts\configure.ps1 -Mode trusted-lan

The token file exists but is empty. Anyone who can reach the published port can view calendar details, so bind to the intended interface and enforce access in your network or reverse proxy.

Bearer token

Use this for the Shield or a reverse proxy that can inject an authorization header:

./scripts/configure.sh --mode bearer --rotate-token
.\scripts\configure.ps1 -Mode bearer -RotateToken

Every route except /api/health then requires:

Authorization: Bearer TOKEN

The Shield wrapper injects this header into every same-origin request. An ordinary browser address bar cannot add it, so browser access in bearer mode should go through an authenticated reverse proxy that injects the header. The configuration scripts intentionally do not print the token.

Configuration

Edit .env for nonsecret settings:

Setting Default Purpose
HOBBS_BIND_ADDRESS 0.0.0.0 Host interface used by Docker
HOBBS_HTTP_PORT 8080 Published HTTP port
ORGANIZER_TIMEZONE America/Chicago Clock and calendar timezone
WEATHER_LOCATION Sapulpa, OK Display label
WEATHER_LATITUDE 35.9987 Sunrise/sunset latitude
WEATHER_LONGITUDE -96.11417 Sunrise/sunset longitude
NWS_FORECAST_URL https://api.weather.gov/gridpoints/TSA/37,98/forecast NWS grid forecast
WEATHER_USER_AGENT example value NWS client identifier; add a monitored contact

Keep these secrets out of .env, Git, image layers, and tickets:

  • secrets/calendar_ical_urls.json
  • secrets/organizer_access_token.txt

Private Google Calendar URLs act like passwords. Because the current URLs were previously shared during development, reset/regenerate each private ICS URL in Google Calendar before the final production cutover, then run the configure script again.

Operations

Status and logs:

docker compose ps
docker compose logs --tail=200 organizer
curl -fsS http://127.0.0.1:8080/api/health

The health endpoint never calls Google, FRED, NWS, or RSS providers. It reports liveness plus booleans for calendar configuration and authentication, but no secret values.

Stop and start:

docker compose stop
docker compose start

Update after replacing the release files:

docker compose build --pull
docker compose up -d --wait
docker image prune

docker image prune is optional and removes only unused images after confirmation.

Backup and restore

The app has no database. Back up only .env and the two runtime secret files.

Linux/macOS:

./scripts/backup.sh
./scripts/restore.sh --force backups/hobbs-organizer-config-YYYYMMDD-HHMMSS.tar.gz

Windows:

.\scripts\backup.ps1
.\scripts\restore.ps1 -Archive .\backups\hobbs-organizer-config-YYYYMMDD-HHMMSS-XXXXXXXX.zip -Force

Backup archives contain private calendar URLs and possibly the bearer token. Store them in an encrypted or tightly access-controlled backup system.

Reverse proxy and HTTPS

HTTPS with a stable internal DNS name is recommended, for example organizer.home.arpa. Sample Caddy and Nginx configurations are in examples/.

If you use an internal certificate authority, install its root certificate in the Shield's user certificate store. The wrapper trusts system and user-installed CAs but always rejects certificate errors; it has no trust-all bypass.

For direct LAN HTTP, the Shield configuration script requires both an explicit -AllowInsecureHttp switch and a private IP, .local, .lan, or .home.arpa host.

NVIDIA Shield deployment

Install Android Platform Tools, enable network debugging, and authorize the computer on the Shield. Upgrading the existing package preserves the hosted rollback token and the per-device 6:30 AM schedule flag.

$adb = "C:\path\to\platform-tools\adb.exe"
& $adb connect 192.168.86.179:5555
& $adb -s 192.168.86.179:5555 install -r .\shield\Hobbs-Family-Organizer-Local-v1.4.apk

For HTTPS with bearer authentication:

.\shield\configure-shield.ps1 `
  -Device 192.168.86.179:5555 `
  -BaseUrl https://organizer.home.arpa/ `
  -TokenFile .\secrets\organizer_access_token.txt `
  -AdbPath $adb

For trusted-LAN HTTP:

.\shield\configure-shield.ps1 `
  -Device 192.168.86.179:5555 `
  -BaseUrl http://192.168.86.50:8080/ `
  -AllowInsecureHttp `
  -AdbPath $adb

The script streams the token through ADB standard input and never places it in the process arguments. Repeat for the second Shield using its address. Do not change the package ID: com.dayline.tv is intentionally preserved so existing app-private data survives adb install -r.

The wrapper is debuggable so the scripts can use Android run-as to provision private files. After the migration, disable network debugging on each Shield and enable it again only during controlled maintenance.

To return a Shield to the existing hosted Site:

.\shield\restore-hosted-site.ps1 -Device 192.168.86.179:5555 -AdbPath $adb

That removes only the local URL/token flags and keeps the old hosted service_token intact.

Build from source

Container application:

npm ci
npm run lint
npm run build
npm start

For local npm start, set CALENDAR_ICAL_URLS to a JSON array and optionally set ORGANIZER_ACCESS_TOKEN. The Compose path is preferred because it loads secrets from files and applies the security profile.

Android source is under shield/source/. It targets Android SDK 35, Java 17, and Gradle 8.9/Android Gradle Plugin 8.7.3. The included APK is already signed with the same certificate as the installed v1.3 app. The private signing key is deliberately not included in this package.

Troubleshooting

  • calendarConfigurationValid: false: rerun scripts/configure.* and confirm the secret contains a JSON array of Google basic.ics URLs.
  • Dashboard returns 401: token mode is enabled; provision the same token to the Shield or reverse proxy.
  • Weather is empty: verify the NWS grid URL, outbound HTTPS, DNS, and WEATHER_USER_AGENT.
  • FRED shows demo data: allow outbound HTTPS to fred.stlouisfed.org; the feed uses public pages and no API key.
  • Shield shows setup required: confirm the APK is installed and rerun shield/configure-shield.ps1.
  • Shield rejects HTTPS: install the internal CA certificate or use a certificate trusted by Android; never bypass validation.
  • Container does not become healthy: run docker compose logs organizer and validate that both secret files exist.