Chibuzor Ezeamaku Case Study
Personal Platform · Self-hosted

LifeOS

A self-hosted “operating system for your life” across Health, Finance, Faith, and Habits — built as a portable Go API with offline-first mobile sync, a spec-first typed client pipeline, and a security-conscious CLI.

Request access → ← All work Foundation complete
What it is

LifeOS organizes personal life domains — Health, Finance, Faith, Habits & Fasting — on top of a small set of shared primitives: Notes, Tasks, People, and Places. It runs as a self-hosted system you own end-to-end: your data lives in your Postgres, your storage bucket, and (on mobile) an encrypted on-device database.

The foundation is complete: authentication, multi-tenant workspaces, CRUD for the core primitives, an offline mobile mirror, and a change-feed sync protocol. The life-domain features build on top of that kernel.

What makes it interesting

One contract, many clients

The API’s OpenAPI 3.1 spec is generated from the Go types themselves (via Huma), and the TypeScript client is generated from that spec. One source of truth per endpoint — the Go struct — consumed by web, mobile, and CLI. No hand-written, drift-prone client code.

Offline-first with a real sync engine

Mobile keeps a full local SQLite mirror and reconciles through a custom last-write-wins protocol: every mutation appends a sync_log row in the same transaction, and clients drain changes past a cursor (push-then-pull). Conflicts resolve on a version column, ties broken by updated_at.

Portable backend by design

Plain Go on chi + Postgres with storage hidden behind an interface. The same Docker image runs locally, on a VM, or on Railway — and the abstraction leaves a Cloudflare Workers path open without rewrites.

Security as a first-class feature

The CLI stores credentials across three pluggable backends (OS keyring → encrypted file → env), the encrypted-file backend using AES-256-GCM + Argon2id, with token redaction in logs and a written threat model. The production image is distroless, non-root, ~2 MB.

Conscious data ownership

Deliberate choices throughout — e.g. CI intentionally does not enable a remote build cache, keeping build data off third-party infrastructure.

Architecture

A pnpm-workspace monorepo with a Go backend and TypeScript/Go clients:

packages/
  api/      Go HTTP API — the system of record.
            chi (router) · Huma v2 (OpenAPI 3.1 from code) · pgx (Postgres) ·
            goose (embedded SQL migrations) · chi-go-auth (sessions/auth).
  cli/      Go CLI (lifeos) — Cobra-based, multi-profile, secure credentials.
  client/   TypeScript HTTP client — types generated from the OpenAPI spec.
  shared/   TypeScript kernel — Zod schemas, ULID/ISO helpers, the sync
            protocol + reconciler, AES-GCM crypto, the SQLite schema.
  web/      Next.js 15 web app.
  mobile/   Expo SDK 52 Android app — encrypted on-device SQLite mirror.

Request / data flow: clients call the Go API → Huma validates against the generated schema → handlers read/write Postgres via pgx → every mutation writes a sync_log entry → mobile pulls those entries into its local SQLite mirror and reconciles.

Tech stack
AreaChoiceNotes
APIGo 1.26 · chi · Huma v2OpenAPI 3.1 generated from code; listens on :8787.
DatabasePostgres 16+Workspace-scoped, multi-tenant.
Migrationsgoose (embedded SQL)Applied idempotently at boot; standalone migrate binary for pre-deploy.
Authchi-go-authEmail/password; session cookies (web), bearer tokens (mobile); sign-up gated by an owner allowlist.
IDsULID, client-sideSortable, collision-resistant, offline-friendly.
WebNext.js 15 · React 18 · Tailwind 4
MobileExpo SDK 52 · RN 0.76op-sqlite (SQLCipher) local mirror, encrypted at rest.
SyncCustom last-write-winsCursor-based change feed on a kernel-row contract.
Object storageS3-compatibleMinIO locally; Railway Buckets / R2 / S3 in prod, behind one interface.
CLIGo · CobraKeyring / encrypted-file / env credentials; table/json/yaml/ndjson output.
DeployRailway · Docker → distrolessSame Dockerfiles run on a plain VM.
Notable engineering decisions
Kernel-row contract

Every persisted table composes the same kernel columns — id, created_at, updated_at, deleted_at, version — so sync, soft-delete, and optimistic concurrency work uniformly across every entity instead of being re-implemented per table.

Money is never a float

All monetary values are stored as amount_minor INTEGER + currency_code TEXT. Timestamps are UTC ISO 8601 on the wire, timestamptz in Postgres, TEXT in SQLite — one consistent representation across two databases.

Soft delete everywhere

Records set deleted_at rather than issuing DELETE, so the change feed can propagate deletions to offline clients and history is never silently lost.

Spec as the integration boundary

A dedicated openapi-export binary derives the spec from the same Go types the handlers use — it runs in milliseconds with zero runtime dependencies (no database needed), keeping codegen decoupled from a running server.

Credential security as a product surface

The CLI’s threat model is documented, tokens are redacted from logs (last 6 chars only), secrets never touch the TOML config file, and the build pipeline runs govulncheck and cross-compiles release binaries with goreleaser.

Security posture
  • No secrets in source or git history; all configuration is environment-driven.
  • Owner-gated sign-up (allowlist); each user gets an isolated workspace.
  • On-device data encrypted at rest (SQLCipher); CLI secrets encrypted with AES-256-GCM + Argon2id when not in the OS keyring.
  • Distroless, non-root runtime image with a minimal attack surface.
  • Private auth dependency injected at build time only — never baked into the image.
Status & roadmap
Done

Go API rebuild, auth + multi-tenant workspaces, Notes/Tasks/People/Places CRUD, the sync change feed, the offline mobile mirror, the CLI, and the spec→client codegen pipeline.