feat(docker-watcher): phase 11 - frontend embed & SSE

Embed SvelteKit static build in Go binary via go:embed. Event bus
for pub/sub with deploy log, instance status, and deploy status events.
SSE endpoints for real-time streaming. Frontend SSE client with
exponential backoff reconnection. Makefile for build pipeline.
Update Phase 12 auth plan with OAuth2/OIDC support.
This commit is contained in:
2026-03-27 22:30:25 +03:00
parent d40cf10f88
commit 5558396bb7
16 changed files with 844 additions and 73 deletions
+10 -3
View File
@@ -33,7 +33,7 @@ A self-hosted tool that automates Docker container deployment with Nginx Proxy M
- [x] Phase 8: REST API Layer [domain: backend] → [subplan](./phase-8-api-layer.md)
- [x] Phase 9: SvelteKit Dashboard & Project Views [domain: frontend] → [subplan](./phase-9-dashboard.md)
- [x] Phase 10: Quick Deploy & Settings Pages [domain: frontend] → [subplan](./phase-10-settings-deploy.md)
- [ ] Phase 11: Frontend Embed & Real-Time Updates [domain: fullstack] → [subplan](./phase-11-embed-sse.md)
- [x] Phase 11: Frontend Embed & Real-Time Updates [domain: fullstack] → [subplan](./phase-11-embed-sse.md)
- [ ] Phase 12: Hardening [domain: backend] → [subplan](./phase-12-hardening.md)
- [ ] Phase 13: Frontend Polish & Modern UI [domain: frontend] → [subplan](./phase-13-ui-polish.md)
- [ ] Phase 14: Volumes & Environment [domain: fullstack] → [subplan](./phase-14-volumes-env.md)
@@ -56,8 +56,8 @@ A self-hosted tool that automates Docker container deployment with Nginx Proxy M
| Phase 7: Deployer & Health | backend | ✅ Complete | ✅ Pass w/ fixes | ⏭️ Skip (Big Bang) | ✅ |
| Phase 8: API Layer | backend | ✅ Complete | ✅ Pass w/ fixes | ⏭️ Skip (Big Bang) | ✅ |
| Phase 9: Dashboard | frontend | ✅ Complete | ⬜ Pending | ⏭️ Skip (Big Bang) | ✅ |
| Phase 10: Settings & Deploy | frontend | ✅ Complete | ⬜ Pending | ⏭️ Skip (Big Bang) | |
| Phase 11: Embed & SSE | fullstack | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
| Phase 10: Settings & Deploy | frontend | ✅ Complete | ⬜ Pending | ⏭️ Skip (Big Bang) | |
| Phase 11: Embed & SSE | fullstack | ✅ Complete | ⬜ Pending | ⏭️ Skip (Big Bang) | ⬜ |
| Phase 12: Hardening | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
| Phase 13: UI Polish | frontend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
| Phase 14: Volumes & Env | fullstack | ⬜ Not Started | ⬜ | ✅ Required (Final) | ⬜ |
@@ -85,6 +85,13 @@ A self-hosted tool that automates Docker container deployment with Nginx Proxy M
**Why:** Missing from feature planner phases but present in root PLAN.md Phase 4
**Impact on existing phases:** Phase 14 becomes the final phase (build/tests required). Phase 13 (UI Polish) remains but no longer the final phase for build enforcement.
### Amendment 4 — 2026-03-27
**Type:** Modified phase
**What changed:** Updated Phase 12 (Hardening) auth tasks to support two modes: Local auth (username/password in SQLite with bcrypt) and OAuth2/OIDC (Authentik or any OIDC provider with configurable discovery URL). Added auth settings UI, user management, OIDC callback flow.
**Why:** Root PLAN.md was updated to require OAuth2/OIDC support alongside local auth
**Impact on existing phases:** Phase 12 task count increased from 10 to 12. Added new files for auth module and login page.
## Final Review
- [ ] Comprehensive code review
+41 -20
View File
@@ -1,6 +1,6 @@
# Phase 11: Frontend Embed & Real-Time Updates
**Status:** ⬜ Not Started
**Status:** Done
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
@@ -9,26 +9,31 @@ Build SvelteKit to static files, embed into the Go binary with `go:embed`, serve
## Tasks
- [ ] Task 1: Configure SvelteKit static adapter to output to `web/build/`
- [ ] Task 2: Add `//go:embed web/build` directive in Go — serve static files
- [ ] Task 3: Create Go handler for serving embedded SPA — serve index.html for all non-API routes (SPA fallback)
- [ ] Task 4: Implement SSE endpoint for deploy logs — `GET /api/deploys/:id/logs` streams deploy progress in real-time
- [ ] Task 5: Implement SSE endpoint for instance status — `GET /api/events` streams instance status changes
- [ ] Task 6: Create event bus/broadcaster in Go — publish events from deployer, subscribe from SSE handlers
- [ ] Task 7: Frontend: connect to SSE for deploy progress — update deploy log view in real-time
- [ ] Task 8: Frontend: connect to SSE for instance status — update dashboard/project views without refresh
- [ ] Task 9: Handle SSE reconnection in frontend — auto-reconnect with backoff on disconnect
- [ ] Task 10: Build script/Makefile — `make build` builds frontend then Go binary
- [x] Task 1: Configure SvelteKit static adapter to output to `web/build/` (already configured)
- [x] Task 2: Add `//go:embed web/build` directive in Go — `web.go` at project root
- [x] Task 3: Create Go handler for serving embedded SPA — `internal/api/static.go` with SPA fallback
- [x] Task 4: Implement SSE endpoint for deploy logs — `GET /api/deploys/:id/logs` (SSE + JSON fallback)
- [x] Task 5: Implement SSE endpoint for instance status — `GET /api/events` streams instance status changes
- [x] Task 6: Create event bus/broadcaster in Go — `internal/events/bus.go` with pub/sub channels
- [x] Task 7: Frontend: connect to SSE for deploy progress — `connectDeployLogs()` in `web/src/lib/sse.ts`
- [x] Task 8: Frontend: connect to SSE for instance status — global SSE in `+layout.svelte` via store
- [x] Task 9: Handle SSE reconnection in frontend — exponential backoff with jitter in `connectSSE()`
- [x] Task 10: Build script/Makefile — `make build` builds frontend then Go binary
## Files to Modify/Create
- `web/svelte.config.js`ensure static adapter outputs to `web/build/`
- `web/svelte.config.js`already configured with static adapter outputting to `web/build/`
- `web.go` — root-level embed directive (`//go:embed web/build`)
- `internal/api/static.go` — embedded static file server with SPA fallback
- `internal/api/sse.go` — SSE endpoints for deploy logs and instance events
- `internal/events/bus.go` — event bus for publishing/subscribing to events
- `web/src/lib/sse.ts` — SSE client helper with auto-reconnect
- `web/src/routes/+layout.svelte` — wire up global SSE connection for instance status
- `web/src/lib/stores/instance-status.ts` — Svelte store for real-time instance status
- `web/src/routes/+layout.svelte` — wired up global SSE connection for instance status
- `Makefile` — build frontend + backend
- `cmd/server/main.go` — wire embedded static serving and event bus
- `cmd/server/main.go` — wired embedded static serving and event bus
- `internal/api/router.go` — added eventBus to Server, SSE routes
- `internal/api/deploys.go` — removed old JSON stub, replaced by SSE handler
- `internal/deployer/deployer.go` — added event publishing for deploy logs, status, instance status
## Acceptance Criteria
- `make build` produces a single Go binary with embedded frontend
@@ -43,13 +48,29 @@ Build SvelteKit to static files, embed into the Go binary with `go:embed`, serve
- Event bus: simple pub/sub with channels — no external dependency needed
- SSE format: `data: {"type": "deploy_log", "payload": {...}}\n\n`
- Keep SSE connections lightweight — use context cancellation for cleanup
- WriteTimeout on HTTP server set to 0 to support long-lived SSE connections
- Deploy logs endpoint serves both SSE (Accept: text/event-stream) and JSON (default)
## Review Checklist
- [ ] All tasks completed
- [ ] Single binary serves both API and frontend
- [ ] SSE handles multiple concurrent clients
- [ ] No goroutine leaks on SSE disconnect
- [ ] Build process is reproducible (Makefile)
- [x] All tasks completed
- [x] Single binary serves both API and frontend
- [x] SSE handles multiple concurrent clients (buffered channels, non-blocking publish)
- [x] No goroutine leaks on SSE disconnect (context cancellation + Unsubscribe)
- [x] Build process is reproducible (Makefile)
## Handoff to Next Phase
<!-- Filled in by the implementation agent after completing this phase. -->
### What was implemented
- **Event bus** (`internal/events/bus.go`): In-process pub/sub with topic filtering, buffered subscriber channels (64 events), non-blocking publish. Supports `EventDeployLog`, `EventInstanceStatus`, and `EventDeployStatus` event types.
- **SSE endpoints**: `GET /api/deploys/{id}/logs` streams deploy logs with JSON fallback; `GET /api/events` streams global instance/deploy status changes.
- **Static file serving**: `web.go` at project root embeds `web/build/`, `internal/api/static.go` serves SPA with fallback. Mounted via chi's `NotFound` handler.
- **Frontend SSE client** (`web/src/lib/sse.ts`): `connectSSE()` with exponential backoff + jitter, `connectDeployLogs()` and `connectGlobalEvents()` convenience functions.
- **Instance status store** (`web/src/lib/stores/instance-status.ts`): Svelte writable store updated by global SSE connection in `+layout.svelte`.
- **Deployer integration**: `deployer.go` now publishes deploy log, deploy status, and instance status events via `EventPublisher` interface.
### Key integration points for next phase
- `events.Bus` is passed to both `api.NewServer` and `deployer.New`
- `api.NewServer` now requires an `*events.Bus` parameter (6th arg before encKey)
- `deployer.New` now requires an `EventPublisher` parameter (6th arg before encKey)
- HTTP server `WriteTimeout` is 0 to support SSE
- The `web.go` file at project root uses package name `dockerwatcher` (imported as `github.com/alexei/docker-watcher`)
+25 -12
View File
@@ -11,22 +11,31 @@ Production hardening — blue-green deploys, promote flow, dashboard auth, grace
- [ ] Task 1: Blue-green deploys — start new container, health check, swap NPM proxy, then stop old container (zero downtime)
- [ ] Task 2: Promote flow — enforce `promote_from` for production deploys (only tags running in source stage are eligible)
- [ ] Task 3: Dashboard auth — basic auth or token-based authentication for the web UI
- [ ] Task 4: Auth middleware — protect all /api/* routes except webhook
- [ ] Task 5: Graceful shutdown — handle SIGTERM/SIGINT, drain in-progress deploys, close DB, stop poller
- [ ] Task 6: Structured logging — JSON logs with deploy context (project, stage, tag, instance ID)
- [ ] Task 7: Config export — download current SQLite state as YAML (reverse of seed import)
- [ ] Task 8: Dockerfile — multi-stage build (build frontend + Go, copy to minimal image)
- [ ] Task 9: docker-compose.yml — production-ready compose file with volumes, network, env
- [ ] Task 10: Final wiring review — ensure all services are properly initialized and shut down
- [ ] Task 3: Local auth — username/password stored in SQLite (bcrypt hashed), login endpoint, session token (JWT or cookie)
- [ ] Task 4: OAuth2/OIDC auth — integration with Authentik or any OIDC provider (configurable client ID, client secret, discovery URL)
- [ ] Task 5: Auth settings UI — settings page to choose auth mode (local/OIDC), configure OIDC provider, manage local users
- [ ] Task 6: Auth middleware — protect all /api/* routes except webhook; check session/JWT/OIDC token
- [ ] Task 7: Graceful shutdown — handle SIGTERM/SIGINT, drain in-progress deploys, close DB, stop poller
- [ ] Task 8: Structured logging — JSON logs with deploy context (project, stage, tag, instance ID)
- [ ] Task 9: Config export — download current SQLite state as YAML (reverse of seed import)
- [ ] Task 10: Dockerfile — multi-stage build (build frontend + Go, copy to minimal image)
- [ ] Task 11: docker-compose.yml — production-ready compose file with volumes, network, env
- [ ] Task 12: Final wiring review — ensure all services are properly initialized and shut down
## Files to Modify/Create
- `internal/deployer/bluegreen.go` — blue-green deploy strategy
- `internal/deployer/promote.go` — promote flow logic
- `internal/api/auth.go` — authentication middleware
- `internal/auth/local.go` — local auth (bcrypt password hashing, session tokens)
- `internal/auth/oidc.go` — OAuth2/OIDC provider integration
- `internal/auth/middleware.go` — auth middleware (session/JWT/OIDC token validation)
- `internal/auth/models.go` — user model, auth settings, session store
- `internal/api/auth.go` — auth API endpoints (login, logout, OIDC callback, user management)
- `internal/config/export.go` — config export to YAML
- `internal/logging/logger.go` — structured JSON logger
- `cmd/server/main.go` — graceful shutdown, structured logging init
- `internal/store/users.go` — user CRUD, auth settings persistence
- `web/src/routes/login/+page.svelte` — login page
- `web/src/routes/settings/auth/+page.svelte` — auth settings UI
- `cmd/server/main.go` — graceful shutdown, structured logging, auth init
- `Dockerfile` — multi-stage build
- `docker-compose.yml` — production compose file
@@ -41,11 +50,15 @@ Production hardening — blue-green deploys, promote flow, dashboard auth, grace
## Notes
- Blue-green: keep old container running until new one passes health check, then swap NPM proxy and stop old
- Auth: start simple (basic auth via env var), can be enhanced later (JWT, OIDC)
- Auth has two modes configurable via settings:
- **Local auth**: username/password in SQLite (bcrypt hashed), JWT session tokens
- **OAuth2/OIDC**: integration with Authentik or any OIDC provider (client ID, secret, discovery URL)
- First launch: create default admin user with configurable password via ADMIN_PASSWORD env var
- OIDC flow: redirect to provider → callback → create/link local user → issue session
- SIGTERM handling: use Go's `os/signal` + `context.WithCancel`
- Structured logging: use `log/slog` (Go stdlib since 1.21)
- Dockerfile: build stage with Node.js + Go, runtime stage with scratch/alpine
- This is the FINAL phase — build and full test suite MUST pass here
- Phase 13 (UI Polish) and Phase 14 (Volumes & Env) follow this phase
## Review Checklist
- [ ] All tasks completed