diff --git a/Makefile b/Makefile index d926d16..b29aa7d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build webhook test test-db lint generate generate-check dev migrate seed export clean tidy ui deploy-ui +.PHONY: build webhook test test-db lint generate generate-check dev migrate seed export clean tidy ui BINARY := oikos GO ?= go @@ -45,15 +45,13 @@ export: dev: docker compose --profile dev up -d -# The SPA is a standalone static build, no longer embedded in the oikos -# binary (plans/2026-07-12-wails-desktop-app.md 0.1) — deployed separately. +# Local sanity-check build of the SPA. Not embedded in the oikos binary +# (plans/2026-07-12-wails-desktop-app.md 0.1) — deploys as its own +# container (compose/web/Dockerfile) via `docker compose --profile full +# up -d web`, same push-to-main pipeline as everything else. ui: cd web && npm run build -deploy-ui: ui - scp -r web/dist/* mac-mini:/var/www/oikos-ui/ - ssh mac-mini sudo systemctl reload caddy - clean: rm -f $(BINARY) $(GO) clean -testcache diff --git a/compose/caddy/Caddyfile.oikos b/compose/caddy/Caddyfile.oikos index ea42850..b2b7f1b 100644 --- a/compose/caddy/Caddyfile.oikos +++ b/compose/caddy/Caddyfile.oikos @@ -3,13 +3,19 @@ # Lives in dtoro/caddy-conf repo; auto-deploys to caddy (LXC 121). THIS COPY # IS A REFERENCE, NOT DEPLOYED FROM HERE — keep it in sync manually. # -# The SPA is no longer embedded in the oikos binary; it's served here as -# static files (`make deploy-ui`). Every API/MCP/agent route now requires a -# bearer token in all cases (api's dev-open bypass was removed) — non-browser -# clients (Wails, curl, a future mobile client) can't complete Authentik's -# browser-session login, so those routes bypass `import authentik` the same -# way the enrollment endpoint always has and rely on api's own combinedAuth -# instead. See the Wails plan's "Plan review" section, gap 1. +# The SPA is no longer embedded in the oikos binary; it's built and served +# by its own container (compose/web/Dockerfile, docker-compose.yml's `web` +# service, mac-mini:8091) rather than as static files read off local disk — +# see that service's comment for why. Every API/MCP/agent route now requires +# a bearer token in all cases (api's dev-open bypass was removed) — +# non-browser clients (Wails, curl, a future mobile client) can't complete +# Authentik's browser-session login, so those routes bypass `import +# authentik` the same way the enrollment endpoint always has and rely on +# api's own combinedAuth instead. See the Wails plan's "Plan review" +# section, gap 1. +# +# mac-mini and the LXC subnet are routed, so these target its direct LAN IP +# rather than the mesh (netbird) hostname. oikos.hubris.network { tls { @@ -17,7 +23,7 @@ oikos.hubris.network { } @enroll path /api/v1/clients/enroll handle @enroll { - reverse_proxy :8090 + reverse_proxy 192.168.178.182:8090 } # Bearer-token clients — api's combinedAuth (internal/httpapi/server.go) # is the real gate for all three; Authentik would just reject non-browser @@ -26,23 +32,22 @@ oikos.hubris.network { # covered by the same check as /api/v1/* and /mcp. @api path /api/v1/* /mcp /agent/* handle @api { - reverse_proxy :8090 + reverse_proxy 192.168.178.182:8090 } - # Everything else: the static SPA shell. No sensitive data lives here — - # real enforcement is the bearer-token check above — Authentik is just a - # first line of defense against anonymous crawlers finding the bundle. + # Everything else: the static SPA shell, served by the `web` container. + # No sensitive data lives here — real enforcement is the bearer-token + # check above — Authentik is just a first line of defense against + # anonymous crawlers finding the bundle. handle { import authentik - root * /var/www/oikos-ui - file_server - try_files {path} /index.html + reverse_proxy 192.168.178.182:8091 } } # Oikos MCP endpoint (agents) — bearer token required (api's combinedAuth), # no separate gate here. mcp.hubris.network { - reverse_proxy :8090 + reverse_proxy 192.168.178.182:8090 } # Nomos's own gateway (workstation access) — still has NO auth of its own @@ -50,5 +55,5 @@ mcp.hubris.network { # can reach this host can talk to nomos directly, bypassing api entirely. # Not fixed by the client/server split — tracked separately. nomos.hubris.network { - reverse_proxy :8092 + reverse_proxy 192.168.178.182:8092 } diff --git a/compose/web/Caddyfile b/compose/web/Caddyfile new file mode 100644 index 0000000..f476be1 --- /dev/null +++ b/compose/web/Caddyfile @@ -0,0 +1,5 @@ +:80 { + root * /srv + file_server + try_files {path} /index.html +} diff --git a/compose/web/Dockerfile b/compose/web/Dockerfile new file mode 100644 index 0000000..b849caa --- /dev/null +++ b/compose/web/Dockerfile @@ -0,0 +1,18 @@ +# Dockerfile for the oikos control-room SPA. Built separately from the +# oikos binary (compose/oikos/Dockerfile) — see docker-compose.yml's `web` +# service. The outer production Caddy (caddy-conf repo, LXC 121) handles +# Authentik + splits /api/*, /mcp, /agent/* off to the api service; this +# container only serves static files with SPA-fallback routing. + +FROM node:22-alpine AS builder + +WORKDIR /build/web +COPY web/package.json web/package-lock.json ./ +RUN npm ci +COPY web/ ./ +RUN npm run build + +FROM caddy:2-alpine + +COPY --from=builder /build/web/dist /srv +COPY compose/web/Caddyfile /etc/caddy/Caddyfile diff --git a/docker-compose.yml b/docker-compose.yml index a28d63f..7989440 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,9 +2,11 @@ # Usage: docker compose up -d postgres (just the DB) # make dev (full dev stack) # -# The SPA isn't part of this stack — it's a standalone static build served -# separately (`make ui`, `npm run dev` in web/), not embedded in the oikos -# image. See plans/2026-07-12-wails-desktop-app.md 0.1/0.6. +# The SPA isn't embedded in the oikos binary (see +# plans/2026-07-12-wails-desktop-app.md 0.1/0.6) but it IS part of this +# stack as its own `web` service (compose/web/Dockerfile), so it deploys +# through the same push-to-main pipeline as everything else. `npm run dev` +# in web/ is still the fast local-iteration path. services: postgres: @@ -145,6 +147,19 @@ services: stop_signal: SIGTERM stop_grace_period: 10s + # Control-room SPA — static build served behind Caddy. The outer + # production Caddy (caddy-conf repo, LXC 121) splits /api/*, /mcp, + # /agent/* off to api:8090 and sends everything else here; this + # container only serves static files with SPA-fallback routing. + web: + build: + context: . + dockerfile: compose/web/Dockerfile + profiles: ["dev", "full"] + ports: + - "8091:80" + stop_signal: SIGTERM + # Redis (required by Infisical — Phase 5) redis: image: redis:7-alpine