Project · Saas Lifebot

SAAS-Lifebot — High-Level Design

Port [[Bobs-lifebot/README]] from a **single-user AWS serverless** stack to a **multi-user SaaS** friends can test. Host on a **home ThinkCentre M70q (Ubuntu)**; develop on a MacBook and ship via **git**. Health data is **special-category** (UK GDPR Art. 9): every user’s data is isolated, and **even the host admin cannot read health values** from disk/DB (admin-blind E2EE). Fitness only — no Study/Udemy.

type hldstatus activelifebot · saas · health · e2ee

Architecture

flowchart TB
  subgraph Clients
    BROWSER[Friend browser<br/>vault unlock + decrypt + charts]
    HAE[Health Auto Export]
    HEVY_API[Hevy API<br/>client-side pull preferred]
  end
  subgraph Edge
    TS[Tailscale WireGuard]
    CFT[Cloudflare Tunnel HTTPS]
  end
  subgraph ThinkCentre["ThinkCentre Ubuntu · LUKS"]
    APP[Next.js SAAS-Lifebot<br/>Docker]
    PG[("Postgres<br/>ciphertext + metadata")]
    APP --> PG
  end
  subgraph External
    CLERK[Clerk auth]
    ANT[Anthropic API<br/>client-side preferred]
  end

  BROWSER -- TLS --> TS
  BROWSER -- TLS --> CFT
  TS & CFT --> APP
  BROWSER --> CLERK
  BROWSER --> ANT
  HAE -- "x-lifebot-key + TLS" --> CFT
  HEVY_API -. browser .-> BROWSER
  APP -. seal-on-ingest .-> PG

Auth model (two tiers)

  1. Browser — Clerk session (sign-up/sign-in). All /api/* (except ingest) require userId. Health rows keyed by ownerId = Clerk userId, never orgId.
  2. Machine ingestPOST /ingest/health and /ingest/hevy use per-user x-lifebot-key (HMAC-stored). Key resolves to one ownerId; writes only for that user.

Admin-blind E2EE (core decision)

  • User creates a vault (password + recovery code). Server stores public key + password-wrapped private key only.
  • Durable health fields are sealed to the user’s public key. Admin psql sees ciphertext.
  • Ingest: normalize in memory → merge state under key derived from presented ingest secret (not stored) → seal display payload to user public key → write.
  • Dashboard: unlock in browser → fetch sealed blobs → decrypt client-side → charts.
  • Coach: decrypt client-side → aggregate summary → Anthropic (prefer browser call).
  • Residual risk: malicious root instrumenting a live ingest request (plaintext briefly in RAM). Honest-but-curious host browsing DB/backups is protected.

See SAAS-Lifebot/adr/0002-admin-blind-e2ee-health-data.

Components

Piece Role
Next.js App Router UI + Route Handlers
Clerk Identity; org onboarding kept light; health ignores org
Drizzle + Postgres Owner-scoped sealed rows
Vault (Web Crypto / libsodium) Keygen, seal, unseal
Ingest normalizers Port of Bobs-lifebot HAE/Hevy logic
Health dashboard KPIs, energy balance, charts, workouts, goals, coach
Settings Vault, ingest key, export/delete, consent
Docker Compose app + db on ThinkCentre
Tailscale / Cloudflare Tunnel Friend + HAE reachability

GDPR / data protection

  • Explicit consent (health processing + AI) before seed/chat/real ingest use.
  • Export / delete in Settings; audit events without health values.
  • Minimisation: daily aggregates + workout summaries only; no raw HAE archive in MVP.
  • Subprocessors: Clerk (auth), Anthropic (coach summaries), host (ThinkCentre) as controller.
  • Privacy page documents TLS, LUKS, E2EE, and operator trust limits.

Decisions

Non-goals

  • Study/Udemy path, HIPAA BAA, public open multi-tenant marketing SaaS, team-shared health, perfect defence against malicious root on live requests.
Compiled from wiki/projects/SAAS-Lifebot/HLD.md · git is the source of truth