- Python 50.2%
- TypeScript 44.6%
- CSS 4.8%
- Dockerfile 0.3%
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.
|
||
|---|---|---|
| .forgejo/workflows | ||
| backend | ||
| frontend | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| docker-compose.override.yml | ||
| docker-compose.yml | ||
| README.md | ||
🏠 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_AUTHin 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:
- builds and pushes
homemanagement-backendandhomemanagement-frontendimages togit.thenymans.com/brian, tagged both:latestand:<commit-sha>; - redeploys the Portainer stack, pinning
IMAGE_TAGto 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, updateNAMESPACEin the workflow andIMAGE_NAMESPACEin.env/ the Portainer stack env.
Configuring Authentik
-
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.
-
Create an Application bound to that provider.
-
(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 agroupsscope/claim. -
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.comfor Verizon,@txt.att.netfor AT&T). - Push — ntfy. 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.