HoffDesk Domain Architecture
Last Updated: 2026-04-24 21:34 UTC
Owner: Matt (Director), Daedalus (Frontend), Socrates (Backend)
Live Subdomains
All routed through a single Cloudflare Tunnel on the Beelink, now using Starlette Host-based routing to dispatch requests to the correct sub-app.
| Subdomain | Port | Service | Status | Purpose |
|---|---|---|---|---|
hoffdesk.com |
:8000 | hoffdesk-api (FastAPI) | ✅ Live | Public blog (SEO) |
notes.hoffdesk.com |
:8000 | hoffdesk-api → notes_app | ✅ Live | Blog admin + content pipeline |
family.hoffdesk.com |
:8001 | hoffdesk-api → family_app | ✅ Live | Family dashboard |
api.hoffdesk.com |
:8000 | hoffdesk-api | ✅ Live | JSON API (blog, content, health) |
cal.hoffdesk.com |
:5232 | Radicale | ✅ Live | CalDAV calendar |
hook.hoffdesk.com |
:8000 | hoffdesk-api | ✅ Live | Webhook receiver |
blog.hoffdesk.com |
— | — | ❌ REMOVED | Was duplicate of notes, no DNS CNAME |
proto.hoffdesk.com |
:8765 | Dev server | ⚠️ Dev only | Retire after family. stable |
Host routing architecture:
Cloudflare Tunnel
│
├── family.hoffdesk.com → family_app (port 8001)
│ ├── / → Dashboard (Jinja2)
│ ├── /family/login/ → Login page
│ ├── /api/today → Calendar + weather + health
│ └── /admin/ → Family automation
│
└── notes.hoffdesk.com, hoffdesk.com, api.hoffdesk.com, hook.hoffdesk.com → notes_app (port 8000)
├── / → Blog (public)
├── /admin/blog/ → Blog admin suite
├── /api/blog/ → Blog JSON API
├── /api/v1/content/* → Content pipeline
└── /webhook → Webhooks / health
Application: hoffdesk-api
Two FastAPI sub-apps, one process. Host-based routing via Starlette Host() dispatches at the edge.
family_app (port 8001 — family.hoffdesk.com)
family.hoffdesk.com
├── / → Dashboard (Jinja2 template, requires auth)
├── /family/login/ → Login page (public)
├── /auth/login → Session auth POST
├── /auth/me → Session validation
├── /api/today → Calendar + weather + health (requires auth)
├── /api/calendar/upcoming → Calendar events (requires auth)
├── /api/weather → Weather data (requires auth)
├── /api/health → System health (requires auth)
├── /admin/* → Family automation (requires auth)
├── /static/ → Dashboard CSS
├── /family/events/removed → Recently removed events (Bearer token)
├── /telegram/callback → Telegram webhook
└── /webhook → Generic webhook receiver
notes_app (port 8000 — notes.hoffdesk.com, hoffdesk.com, api.hoffdesk.com, hook.hoffdesk.com)
notes.hoffdesk.com
├── / → Blog index (public HTML)
├── /article/{slug} → Article page (public HTML)
├── /categories → Category list
├── /tags → Tag list
├── /api/blog/ → Blog JSON API
│ ├── /posts → Post listing
│ ├── /posts/{slug} → Single post
│ ├── /categories → Category list
│ ├── /tags → Tag list
│ ├── /feed.xml → RSS 2.0 feed
│ ├── /sitemap.xml → Sitemap
│ └── /admin/* → Blog admin API (requires auth)
├── /admin/blog/ → Blog admin panel (requires auth)
│ ├── /login → Admin login page (now session cookie)
│ ├── /posts → Post list
│ ├── /posts/new → New post editor
│ ├── /posts/{slug}/edit → Edit existing post
│ ├── /images → Image upload
│ └── /content/* → Content pipeline v2
├── /api/v1/content/ → Content pipeline v2 API
├── /health → Health check
└── /webhook → Webhook receiver (hook.hoffdesk.com)
Auth Model (Finalized 2026-04-24)
Session cookies for humans, Bearer tokens for machines. No exceptions.
| Route | Auth Method | Who | Notes |
|---|---|---|---|
/ and /api/blog/* (public) |
None | Anyone | SEO + RSS |
/admin/blog/* |
Session cookie | Matt (admin) | Redirects to login if unauthenticated |
/admin/family/* |
Session cookie | Matt, Aundrea (family) | Role check (admin/family) |
/api/v1/content/* |
Session cookie (browser) or Bearer (API) | Matt | Dual-mode |
Dashboard / + /api/today |
Session cookie | Authenticated users | Requires login |
/family/login/ |
None | Anyone | Public login page |
/family/events/removed |
Bearer token | Dashboard proxy | Machine-to-machine |
/telegram/callback |
Internal secret | Telegram bot | Unchanged |
/webhook |
Internal secret | Cloudflare Worker | Unchanged |
Credentials
| User | Password | Roles | Access |
|---|---|---|---|
matt |
hoffdesk-matt-2026 |
admin, editor, family | Everything |
aundrea |
hoffdesk-aundrea-2026 |
family | Dashboard only |
Navigation Flows
Public Users → hoffdesk.com
hoffdesk.com
→ Blog index (articles, categories, tags)
→ Article pages (full posts)
→ RSS feed (/api/blog/feed.xml)
→ Sitemap (/api/blog/sitemap.xml)
Matt (Admin) → notes.hoffdesk.com/admin/blog/
notes.hoffdesk.com/admin/blog/
→ Login → session cookie set
→ Post list → New Post / Edit Post
→ Content Pipeline (Magic Wand + Struggle v2)
→ Image upload
notes.hoffdesk.com/admin/family/
→ Family pipeline status
→ Email webhook config
Aundrea at 7 AM → family.hoffdesk.com
family.hoffdesk.com
→ (no session) → redirect to /family/login/
→ Login → session cookie set → redirect to /
→ Calendar card (HTMX, polls /api/today every 30s)
→ Weather card (HTMX, polls /api/today every 30s)
→ System health card (HTMX, polls /api/today every 30s)
→ Recently Removed widget (JS fetch every 60s)
API Consumers → api.hoffdesk.com
api.hoffdesk.com
→ /api/blog/* (public blog data)
→ /api/v1/content/* (content pipeline)
→ /family/events/* (family data)
→ /health (service health)
Architecture Decisions Log
| Date | Decision | Made By |
|---|---|---|
| 2026-04-19 | Sovereign stack: FastAPI + HTMX + Tailwind, no SPA frameworks | Matt |
| 2026-04-19 | Mobile-first, dark mode default, no toggle | Matt |
| 2026-04-20 | Blog at notes.hoffdesk.com, admin at /admin/blog/ |
Matt |
| 2026-04-21 | Dashboard at family.hoffdesk.com, bare domain reserved for landing |
Matt |
| 2026-04-24 | Session cookies for browsers, Bearer tokens for API/webhooks | Matt |
| 2026-04-24 | Roles: admin (full), family (dashboard only) |
Matt |
| 2026-04-24 | Host-based routing via Starlette Host() for subdomain separation |
Socrates |
| 2026-04-24 | family.hoffdesk.com → family_app (port 8001), everything else → notes_app (port 8000) |
Socrates |
| 2026-04-24 | Dashboard converted to Jinja2 templates (base.html.j2 + index.html.j2) | Daedalus |
| 2026-04-24 | blog.hoffdesk.com removed from tunnel config (zombie subdomain) |
Matt |
| 2026-04-24 | hoffdesk.com apex A record added pointing to Cloudflare proxy |
Daedalus |
Sprint Priorities (as of 2026-04-24)
Completed Today
- ✅ Host-based routing deployed (Socrates)
- ✅
family.hoffdesk.comin tunnel + DNS (Wadsworth) - ✅
hoffdesk.comA record added (Daedalus) - ✅ Auth unified — session cookies for browsers, Bearer tokens for APIs (Socrates + Daedalus)
- ✅ All
?token=andX-Admin-Tokenrefs removed from templates (Daedalus) - ✅ Hardcoded URLs updated:
magic_wandAPI_BASE → relative, admin_base Dashboard link →family.hoffdesk.com(Daedalus) - ✅ Dashboard login page delivered (Daedalus)
- ✅ Dashboard converted to Jinja2 templates (Daedalus)
Next Sprint
- Wire pipeline Phase 2 frontend templates to backend routes (Socrates)
- Dashboard Jinja2 → router integration (Socrates — see
JINJA2-HANDOFF.md) - Retire
proto.hoffdesk.com(dev server no longer needed) - Build Week View + Conflict Panel + Undo Toast (Family Assistant v1.5)
- Public landing page at
hoffdesk.com
Remaining Pain Points
-
Dashboard still served as static HTML (via
read_text()). Jinja2 templates are ready but router needs updating. Seeshared/project-docs/dashboard/JINJA2-HANDOFF.md. -
Pipeline Phase 2 templates not wired. My struggle-first pipeline templates sit in the filesystem but have no backend routes rendering them.
-
Template directory fragmentation. Dashboard templates in
shared/project-docs/dashboard/templates/, blog inshared/project-docs/blog/templates/, admin workspace copies mirroring them. Need a single source of truth convention. -
api.hoffdesk.comserves HTML at/when you'd expect JSON-only. Works but violates the "each subdomain one job" principle.