No description
  • Python 50.2%
  • TypeScript 44.6%
  • CSS 4.8%
  • Dockerfile 0.3%
Find a file
Brian 017321eced
All checks were successful
Build and Deploy / build-backend (push) Successful in 9s
Build and Deploy / build-frontend (push) Successful in 16s
Build and Deploy / deploy (push) Successful in 13s
Bump @brian/claude-chat-ui to 0.1.1 for the Android voice-duplication fix
0.1.0 still reproduced the exact duplication bug this dependency was
meant to fix ("thisthis isthis is a test") - confirmed on a live
Android device after the prior deploy. 0.1.1 fixes the hook itself
(stops summing result entries); this just picks it up.
2026-07-20 14:39:39 -06:00
.forgejo/workflows Fix CI: install buildx and a docker-container builder before building 2026-07-20 14:25:40 -06:00
backend Adopt shared claude-chat-client-py / @brian/claude-chat-ui packages 2026-07-20 14:20:00 -06:00
frontend Bump @brian/claude-chat-ui to 0.1.1 for the Android voice-duplication fix 2026-07-20 14:39:39 -06:00
.env.example fix: compute "today" from the configured app timezone, not raw UTC 2026-07-19 10:46:39 -06:00
.gitignore Add editable links/trackers, image icons, tracker alerts, settings 2026-06-09 19:37:11 -06:00
CLAUDE.md feat: import calendar events from an uploaded .ics file 2026-07-19 17:05:58 -06:00
docker-compose.override.yml Initial commit: Home Manager app 2026-06-09 16:35:20 -06:00
docker-compose.yml Add editable links/trackers, image icons, tracker alerts, settings 2026-06-09 19:37:11 -06:00
README.md feat: complete To Do Lists feature (router, page, sharing, completion tracking) 2026-07-08 16:09:00 -06:00

🏠 Home Manager

A self-hosted app for keeping on top of your house:

  • Trackers — recurring upkeep with a "next due" date (water the plants every 7 days, service the AC, replace filters). One tap to mark done, editable, with a "recent activity" feed. Optional alerts when something comes due, with a configurable lead time and time of day to fire. Each tracker is either household (everyone can see and complete it) or private (only the creator); only the creator can delete it.
  • History — a log of everything done to the house, big (roof replaced) and small, with cost/vendor/notes. Tap any entry to see its full details and edit it (or attach/remove receipts). (Tracker activity is kept separate, on the Trackers page.)
  • Goals — one-off and repeating goals (daily / weekly / monthly). Daily goals show a week grid to tick off each day; weekly/monthly goals show done/not for the current period. Goals are owned by their creator and can be shared with other members for accountability (a sharee can see the goal and check it off; only the owner can edit, delete, or change sharing).
  • To Do — simple checklists. Each list has a name and holds free-text items you can add, edit, check off, and delete; completed items drop to a struck-through "Completed" area at the bottom of the list. Lists are owned by their creator and can be private or shared — a sharee can always see the list and check items off (we record who completed each item and when), and can be granted edit rights to add/edit/remove items too. Only the owner can rename, delete, or change sharing.
  • Favorites — organise your favourite things into categories (Restaurants, Films, Books, …) and add items with a name, photo, a 1-10 rating, and notes. Each category and each individual item is owned by its creator and can be shared with other members to view: share a whole category (they see every item) or just a single item (they see only that one). Only the owner can edit, delete, or change sharing.
  • Dashboard — quick links to your other household apps (Plex, Jellyfin, Audiobookshelf, Home Assistant, …) with custom icon images (upload or URL), editable, plus an at-a-glance "needs attention" view.
  • Alerts — per-tracker reminders delivered to each user's own channels: email, SMS (via a carrier email-to-text gateway), and mobile push (ntfy). Configure your contacts and channels under Settings. Alert times are interpreted in a household timezone that admins set under Settings.

Built to work well on mobile and desktop. Logins are OIDC via Authentik, and household members are auto-provisioned on first sign-in.

Stack

Layer Tech
Backend FastAPI · SQLAlchemy 2 (async) · Postgres · PyJWT
Frontend React + Vite + TypeScript · TanStack Query · react-oidc-context
Auth Authentik OIDC (Bearer JWT, or forward-auth headers)
Deploy Docker Compose (db + backend + frontend/nginx)

Quick start (Docker)

cp .env.example .env
# edit .env: set POSTGRES_PASSWORD and the Authentik OIDC values
docker compose up --build

App is served at http://localhost:8080. The frontend's nginx proxies /api to the backend, so there's a single origin to expose.

Try it without Authentik

To kick the tyres before wiring up OIDC, run in dev-auth mode — every request is treated as a local admin user:

HM_DEV_AUTH=true docker compose up --build

⚠️ Never enable HM_DEV_AUTH in production — it disables authentication.


Local development (no Docker)

Backend (Python 3.12+):

cd backend
python -m venv .venv && source .venv/bin/activate   # repo already ships a .venv
pip install -e ".[dev]"
HM_DEV_AUTH=true HM_DATABASE_URL="sqlite+aiosqlite:///./dev.db" \
  uvicorn app.main:app --reload

(For Postgres locally, point HM_DATABASE_URL at a running instance and drop the SQLite URL.)

Frontend:

cd frontend
npm install
npm run dev   # http://localhost:5173, proxies /api -> http://localhost:8000

Tests / typecheck:

cd backend && pytest          # API smoke tests on in-memory SQLite
cd frontend && npm run build  # tsc typecheck + production build

CI/CD (Forgejo Actions → Portainer)

.forgejo/workflows/build-deploy.yml runs on every push to main (and via manual dispatch). It:

  1. builds and pushes homemanagement-backend and homemanagement-frontend images to git.thenymans.com/brian, tagged both :latest and :<commit-sha>;
  2. redeploys the Portainer stack, pinning IMAGE_TAG to that commit's SHA and pulling the new images.

The repo's docker-compose.yml is the stack Portainer runs — it references the registry images. Local development builds from source instead via docker-compose.override.yml (merged automatically by docker compose up --build); that override is never sent to Portainer.

Required Forgejo repo secrets:

Secret Purpose
REGISTRY_USER Registry login user for docker push
REGISTRY_PASSWORD Registry login password/token
PORTAINER_TOKEN Portainer API key (X-API-Key)
PORTAINER_STACK_ID ID of the existing Portainer stack to update
PORTAINER_ENDPOINT_ID Portainer environment/endpoint ID

Postgres and OIDC values (POSTGRES_PASSWORD, HM_OIDC_ISSUER, …) live as env vars on the Portainer stack itself — the deploy job preserves them and only overrides IMAGE_TAG, so secrets never need mirroring into CI. The runner must use the docker label and be able to reach the registry and Portainer.

If your repo owner isn't brian, update NAMESPACE in the workflow and IMAGE_NAMESPACE in .env / the Portainer stack env.


Configuring Authentik

  1. In Authentik, create an OAuth2/OpenID Provider:

    • Client type: Public (the SPA uses Authorization Code + PKCE).
    • Redirect URI: https://homemanager.example.com/ (your app's origin, with trailing slash).
    • Note the Client ID and the provider's Issuer URL. The last path segment is your Authentik application slug and must match it exactly, e.g. https://auth.thenymans.com/application/o/home-management/. Confirm by opening <issuer>/.well-known/openid-configuration — it must return JSON.
  2. Create an Application bound to that provider.

  3. (Optional) Create a group — e.g. home-manager-admins — and add the members who should be able to manage dashboard links. Make sure your provider emits a groups scope/claim.

  4. Fill in .env:

    HM_OIDC_ISSUER=https://auth.example.com/application/o/home-manager/
    HM_OIDC_AUDIENCE=<the Client ID>
    HM_ADMIN_GROUP=home-manager-admins
    HM_CORS_ORIGINS=https://homemanager.example.com
    

The SPA discovers these at runtime from /api/config, runs the OIDC code flow, and sends the access token as a Bearer token. The backend validates the token signature against Authentik's JWKS and creates the User row on first sight. Admins are anyone whose token carries HM_ADMIN_GROUP in its groups claim.

Alternative: forward-auth

If you instead front the backend with an Authentik proxy/forward-auth outpost, set HM_TRUST_FORWARD_AUTH=true. The backend will then trust the X-authentik-* headers the outpost injects. Only do this when the outpost is the only network path to the backend.


Notifications (tracker alerts)

Alerts are opt-in per user and per tracker:

  • Per tracker (Trackers page → edit): toggle Send alerts when due and set a lead time (e.g. remind 3 days before due). Requires a repeat interval.
  • Per user (Settings page): turn on Email / SMS / Push and provide each channel's contact detail. A Send test button verifies delivery.

A background scheduler notifies every user whose channels are configured when a tracker reaches due date lead days. Each tracker fires once per due cycle; marking it done re-arms it.

Server settings are admin-managed in the app, not via env. An admin opens Settings → Server notifications to turn the scheduler on/off, set the scan interval, and configure SMTP and ntfy (secrets are write-only — they're never returned to the browser, and a blank field leaves the stored value unchanged). The HM_ALERTS_ENABLED / HM_SMTP_* / HM_NTFY_* env vars are only seed defaults applied on first startup; after that the database is the source of truth. Changes in the admin UI take effect without a restart.

Admins: the first user to sign in is automatically made an admin (so a fresh install always has someone who can manage settings). Beyond that, membership of HM_ADMIN_GROUP grants admin. Admin is sticky — it's never revoked on a later login, which also protects against an OIDC token that omits the groups claim.

Channels:

  • Email — standard SMTP (HM_SMTP_*).
  • SMS — delivered as an email to a carrier email-to-text gateway, so it reuses SMTP — no Twilio/etc. Each user enters their gateway address (e.g. 5551234567@vtext.com for Verizon, @txt.att.net for AT&T).
  • Pushntfy. The admin sets the ntfy server; each user picks a personal topic and subscribes to it in the ntfy mobile app. Choose an unguessable topic — anyone who knows it can read the alerts. Published via ntfy's JSON API (UTF-8, emoji-safe).
    • Private ntfy servers: each user can supply their own credentials in Settings — either an access token or a username + password (HTTP basic). A user's own credentials take precedence over the admin's server-wide token. These are write-only (never returned to the browser).

Uploaded dashboard/tracker icons are stored under HM_UPLOAD_DIR (a Docker volume in the bundled compose) and served from /api/uploads/....


Configuration reference

All backend settings are environment variables prefixed HM_ (see .env.example):

Variable Default Purpose
HM_DATABASE_URL local Postgres SQLAlchemy async URL
HM_CORS_ORIGINS http://localhost:5173 Allowed SPA origins (comma-separated)
HM_OIDC_ISSUER Authentik provider issuer URL
HM_OIDC_AUDIENCE Authentik Client ID (expected aud)
HM_OIDC_JWKS_URL derived from issuer Override JWKS endpoint if needed
HM_ADMIN_GROUP home-manager-admins OIDC group granting admin rights
HM_TRUST_FORWARD_AUTH false Trust X-authentik-* proxy headers
HM_DEV_AUTH false Dev only: bypass auth as a local admin
HM_UPLOAD_DIR /data/uploads Where uploaded icon images are stored
HM_ALERTS_ENABLED false Run the background tracker-alert scheduler
HM_ALERT_INTERVAL_MINUTES 60 How often to scan for due trackers
HM_SMTP_HOST / _PORT / _USER / _PASSWORD SMTP server for email + SMS gateway
HM_SMTP_FROM Home Manager <…@localhost> From address on outgoing mail
HM_SMTP_STARTTLS / _SSL true / false SMTP transport security
HM_NTFY_SERVER https://ntfy.sh ntfy server for push notifications
HM_NTFY_TOKEN Optional bearer token for a protected ntfy

API

Interactive docs at /docs when the backend is running. Endpoints are under /api: events, trackers (+ POST /api/trackers/{id}/done), goals, favorites (/categories and /items, each with /shares), dashboard-links, and users/me.

Schema note: tables are auto-created on startup, which is ideal for a single-household self-hosted app. If you later need versioned schema changes, add Alembic — the models live in backend/app/models.py.