Skip to content

Configuration reference

Every configuration knob in one place. There are only three places anything is configured:

WhereWhat lives there
.envAll secrets and settings (copy from .env.example)
config/members.jsonThe family roster (copy from config/members.example.json)
data/photos/Screensaver images (just drop files in — no config)

.env.example is the source of truth

The repo's .env.example is the canonical, always-current list with inline comments. This page mirrors it with a bit more context. If the two ever disagree, believe .env.example.

How the <ID> variables work

Several variables end in a member id — TODOIST_UID_<ID>, MEMBER_PIN_<ID>, CALENDAR_ICS_URL_<ID>. The <ID> is a roster id from config/members.json, uppercased. So member "id": "kid1" uses TODOIST_UID_KID1, MEMBER_PIN_KID1, and CALENDAR_ICS_URL_KID1. Any member left blank simply falls back to demo data.

Settings screen overrides .env

A handful of timing values are fresh-install defaults only. Once someone saves the wall's Settings screen (parent-PIN gated), the saved value goes to the database and wins over the env var. These are marked DB-overridable below: SLEEP_*, SCREENSAVER_*, CELEBRATE_CHORES, and the PUSH_* toggles.


Core

VariableDefaultWhat it does
TZAmerica/New_YorkIANA timezone (e.g. America/Los_Angeles). Must be a real tzdata name — an invalid one silently falls back to UTC and breaks the "completed today" midnight cutoff.
BIND_ADDR(blank = all interfaces)Which host interfaces publish port 8080. Blank = whole LAN. Set 127.0.0.1 to restrict to this host and reach it over Tailscale instead. See Networking.

Roster & access

The roster itself is config/members.json (see First-time setup) — not an env var.

VariableDefaultWhat it does
MEMBER_PIN_<ID>(blank)4-digit PIN for that member's companion-PWA login. Blank = they can't log in on mobile. The wall itself stays login-free.
BUDGET_UNLOCK_MEMBERSdad,momComma-separated member ids allowed to PIN-unlock the Budget tab on the wall. Each needs a MEMBER_PIN_<ID>.

Todoist — tasks & chores

Full walkthrough: Todoist integration.

VariableDefaultWhat it does
TODOIST_TOKEN(blank)API token (Todoist → Settings → Integrations → Developer). Blank = demo tasks.
TODOIST_TODO_PROJECTtodosShared Todoist project name for to-dos.
TODOIST_ROUTINE_PROJECTchoresShared Todoist project name for routines/chores.
TODOIST_UID_<ID>(blank)That member's Todoist user id. Discover with curl -s localhost:8080/api/todoist/collaborators. Blank = demo data for that person.

Calendar

Full walkthrough: Calendar integration.

VariableDefaultWhat it does
CALENDAR_ICS_URL_<ID>(blank)A member's personal iCal feed(s). Events take their roster color and land in their Day-view column. Multiple URLs separated by ;.
CALENDAR_ICS_URL(blank)Shared feed(s) nobody owns (e.g. a family calendar). Shows in the "Everyone" column.

Mealie — meals & recipes

Full walkthrough: Mealie integration.

VariableDefaultWhat it does
MEALIE_URL(blank)Base URL the container uses to reach Mealie, no trailing slash. Blank = demo meals.
MEALIE_TOKEN(blank)Mealie long-lived API token (Mealie → Settings → API Tokens).
MEALIE_APP_URL(blank)Browser-facing HTTPS Mealie URL for the PWA's Meals launcher (e.g. the ts.net URL). Blank = the Meals tab is hidden.

Budget — Monarch Money

Full walkthrough: Budget integration.

VariableDefaultWhat it does
MONARCH_EMAIL(blank)Monarch login email. Blank = demo budget.
MONARCH_PASSWORD(blank)Monarch login password.
MONARCH_TOTP_SECRET(blank)If Monarch login uses MFA, the TOTP secret (not a 6-digit code) so the sidecar generates its own codes.
FETCH_INTERVAL_MIN30How often (minutes) the sidecar refreshes the budget snapshot.

Weather

Full walkthrough: Weather integration. Uses Open-Meteo — no API key or signup.

VariableDefaultWhat it does
WEATHER_LAT40.7128Latitude. Blank lat/lon shows a setup placeholder.
WEATHER_LON-74.0060Longitude.
WEATHER_UNITSimperialimperial or metric.
WEATHER_PLACE(blank)Optional label shown on the card.

Sleep & screensaver

All DB-overridable via the Settings screen.

VariableDefaultWhat it does
SLEEP_START_HOUR22Hour (24h, local) the app-level blackout starts. Wraps past midnight.
SLEEP_END_HOUR7Hour the blackout ends.
SCREENSAVER_IDLE_MIN3Idle minutes before the photo slideshow starts.
SCREENSAVER_CYCLE_SEC20Seconds per photo.

Screensaver photos

No env var — drop image files (.jpg, .jpeg, .png, .webp) into data/photos/ on the host. The folder is bind-mounted into the container, so new files show up without a rebuild. An empty folder falls back to a dark ambient clock face.

bash
mkdir -p data/photos
cp ~/family-pics/*.jpg data/photos/

Why not Google Photos?

Google locked down the Photos API in 2025 in a way that doesn't work for an always-on kiosk, so photos are a plain host folder instead. Pointing the screensaver at a self-hosted Immich instance is on the roadmap.

Celebrations

VariableDefaultWhat it does
CELEBRATE_CHORES1DB-overridable. 1/0 — confetti + chime when a kid (roster "kid": true) marks a chore done. See Settings.

Push notifications (companion PWA)

Requires the Tailscale HTTPS setup first — see Networking and Self-hosting → Push.

VariableDefaultWhat it does
VAPID_PUBLIC_KEY(blank)VAPID key pair — generate once with npx web-push generate-vapid-keys. Blank = push quietly disabled.
VAPID_PRIVATE_KEY(blank)The private half of the pair.
VAPID_SUBJECTmailto:you@example.comContact URI required by the push spec.
PUSH_MEMBERSdad,momComma-separated member ids who receive scheduled notifications.
CHORE_REMINDER_HOUR8Hour (24h, local) to send the "chores still open" reminder.
DINNER_DIGEST_HOUR17Hour to send tonight's-dinner digest.
PUSH_CHORE_REMINDER1DB-overridable. 1/0 kill-switch for the chore reminder.
PUSH_DINNER_DIGEST1DB-overridable. 1/0 kill-switch for the dinner digest.

Journal recap

VariableDefaultWhat it does
RECAP_HOUR19Hour (24h, local) the weekly/monthly recap auto-posts to the Journal (Sundays + the 1st).

Rewards catalog

Not an env var — edit server/config/rewards.js to define what chore points can buy. See Rewards.

Presence sensor (optional, future hardware)

VariableDefaultWhat it does
PRESENCE_TOKEN(blank)If set, a PIR/mmWave sensor POSTing to /api/presence (to wake the screensaver) must send it as an X-Presence-Token header. Blank = no token required (fine on a trusted LAN). See the roadmap.

Released under the MIT License.