8715589cbb
- /admin: first-visit password setup (scrypt), signed 30-day session cookie, calendars add/remove/reorder/rename/color/hide, masked URLs, per-feed test, history (20) with restore; /photo-admin -> /admin#photos placeholder - settings in local /data (settings.json + auth.json + history/), seeded once from the calendar secret; dashboard + health read from settings - start.mjs drops privileges after reading secrets and runs a front proxy that stamps the real client IP; admin refuses non-LAN and proxied requests - deploy: HOBBS_DATA_DIR=/var/lib/hobbs-organizer/data, image 1.1.0 (1.0.0 kept) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E9NnVEico3WvFxLyjy1iW3
275 lines
11 KiB
Markdown
275 lines
11 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```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:
|
|
|
|
```bash
|
|
./scripts/configure.sh --mode trusted-lan
|
|
```
|
|
|
|
```powershell
|
|
.\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:
|
|
|
|
```bash
|
|
./scripts/configure.sh --mode bearer --rotate-token
|
|
```
|
|
|
|
```powershell
|
|
.\scripts\configure.ps1 -Mode bearer -RotateToken
|
|
```
|
|
|
|
Every route except `/api/health` then requires:
|
|
|
|
```text
|
|
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.
|
|
|
|
## Admin screen (v1.1.0)
|
|
|
|
Open `http://SERVER-IP:8080/admin` from a phone or laptop on the home network.
|
|
|
|
- **First visit:** choose the admin password. This works only while no password exists, and only from an allowed network.
|
|
- **Calendars tab:** add, remove, reorder, rename, recolor or hide calendars.
|
|
- Saved addresses are masked and never shown again.
|
|
- **Test** fetches a feed and reports its name and how many events are upcoming.
|
|
- Every save keeps the previous version. **History → Restore** brings any of the last 20 back.
|
|
- **Account tab:** change the password (this signs out other devices) or sign out.
|
|
- **Forgot the password:** `docker exec -u 10001 <container> node container/reset-admin.mjs`, then visit `/admin` again.
|
|
|
|
Settings live in the `/data` volume (`HOBBS_DATA_DIR`, default `./data`). It must be writable by uid 10001 and should be on local disk.
|
|
|
|
| File | Contents |
|
|
| --- | --- |
|
|
| `settings.json` | Calendars, including their private URLs. Treat it like a secret. |
|
|
| `auth.json` | The scrypt password hash and the session key. |
|
|
| `history/` | The last 20 saved versions. |
|
|
|
|
The `calendar_ical_urls` secret is now optional. It is used only to seed `settings.json` on first start.
|
|
|
|
Admin is refused unless the TCP client address is inside `ADMIN_ALLOWED_CIDRS`. It is also refused, with a 404, for any request carrying `X-Forwarded-For`, `Forwarded` or `X-Real-IP`, so it cannot be reached through a reverse proxy. The container's own front proxy (port 3000, with Next.js on 127.0.0.1:3001) records the real client address, and forged copies of that header are discarded.
|
|
|
|
## 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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
docker compose stop
|
|
docker compose start
|
|
```
|
|
|
|
Update after replacing the release files:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
./scripts/backup.sh
|
|
./scripts/restore.sh --force backups/hobbs-organizer-config-YYYYMMDD-HHMMSS.tar.gz
|
|
```
|
|
|
|
Windows:
|
|
|
|
```powershell
|
|
.\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.
|
|
|
|
```powershell
|
|
$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:
|
|
|
|
```powershell
|
|
.\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:
|
|
|
|
```powershell
|
|
.\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:
|
|
|
|
```powershell
|
|
.\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:
|
|
|
|
```bash
|
|
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.
|