chore: add feature planner setup for docker-watcher-core
Create structured plan files with 12 phases covering the full implementation: scaffold, store, crypto, Docker/NPM clients, registry poller, webhook, deployer, API layer, SvelteKit frontend, embedding, and hardening.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Feature Context: Docker Watcher Core
|
||||
|
||||
## Configuration
|
||||
- **Development mode:** Automated
|
||||
- **Execution mode:** Orchestrator
|
||||
- **Strategy:** Big Bang (with per-phase code quality reviews)
|
||||
- **Build (Go):** `go build ./cmd/server/`
|
||||
- **Test (Go):** `go test ./...`
|
||||
- **Lint (Go):** `golangci-lint run`
|
||||
- **Build (Frontend):** `cd web && npm run build`
|
||||
- **Test (Frontend):** `cd web && npm test`
|
||||
- **Dev server:** `go run ./cmd/server/`
|
||||
|
||||
## Current State
|
||||
Greenfield project. Only PLAN.md exists with the architecture document.
|
||||
|
||||
## Temporary Workarounds
|
||||
None yet.
|
||||
|
||||
## Cross-Phase Dependencies
|
||||
- Phase 2 depends on Phase 1 (store CRUD for seed import)
|
||||
- Phases 3 and 4 are independent of each other (can run in parallel)
|
||||
- Phase 5 depends on Phase 1 (store for poll state)
|
||||
- Phase 6 depends on Phase 3 (Docker inspect for auto-creation) and Phase 1 (store)
|
||||
- Phase 7 depends on Phases 3, 4, 5 (Docker, NPM, registry clients)
|
||||
- Phase 8 depends on Phases 1-7 (wires everything to HTTP)
|
||||
- Phases 9 and 10 are independent of each other (can run in parallel)
|
||||
- Phase 11 depends on Phases 8, 9, 10 (embeds frontend, SSE wires to API)
|
||||
- Phase 12 depends on all prior phases
|
||||
|
||||
## Deferred Work
|
||||
None yet.
|
||||
|
||||
## Failed Approaches
|
||||
None yet.
|
||||
|
||||
## Review Findings Log
|
||||
None yet.
|
||||
|
||||
## Phase Execution Log
|
||||
| Phase | Agent Used | Test Writer | Parallel | Notes |
|
||||
|-------|-----------|-------------|----------|-------|
|
||||
| — | — | — | — | No phases executed yet |
|
||||
|
||||
## Environment & Runtime Notes
|
||||
- Platform: Windows 10 (development), Linux (deployment target)
|
||||
- Docker socket: `/var/run/docker.sock` (Linux) — development may need Docker Desktop
|
||||
- Go version: TBD (will be determined in Phase 1)
|
||||
|
||||
## Implementation Notes
|
||||
- Big Bang strategy: intermediate phases skip build/tests, code quality reviews after every phase
|
||||
- Final phase (12) is the only phase where build + full test suite must pass
|
||||
- Phases 3+4 and 9+10 identified for parallel execution
|
||||
@@ -0,0 +1,64 @@
|
||||
# Feature: Docker Watcher Core
|
||||
|
||||
**Branch:** `feature/docker-watcher-core`
|
||||
**Base branch:** `main`
|
||||
**Created:** 2026-03-27
|
||||
**Status:** 🟡 In Progress
|
||||
**Strategy:** Big Bang (with per-phase code quality reviews)
|
||||
**Mode:** Automated
|
||||
**Execution:** Orchestrator
|
||||
|
||||
## Summary
|
||||
|
||||
A self-hosted tool that automates Docker container deployment with Nginx Proxy Manager integration. Detects new images from Gitea/GitHub registries, deploys containers, and configures reverse proxy routing — all from a web dashboard. Supports multiple simultaneous versions of the same project.
|
||||
|
||||
## Build & Test Commands
|
||||
- **Build (Go):** `go build ./cmd/server/`
|
||||
- **Test (Go):** `go test ./...`
|
||||
- **Lint (Go):** `golangci-lint run`
|
||||
- **Build (Frontend):** `cd web && npm run build`
|
||||
- **Test (Frontend):** `cd web && npm test`
|
||||
- **Dev server:** `go run ./cmd/server/`
|
||||
|
||||
## Phases
|
||||
|
||||
- [ ] Phase 1: Project Scaffold & SQLite Store [domain: backend] → [subplan](./phase-1-scaffold-store.md)
|
||||
- [ ] Phase 2: Crypto & Config Seed Loader [domain: backend] → [subplan](./phase-2-crypto-config.md)
|
||||
- [ ] Phase 3: Docker Client [domain: backend] → [subplan](./phase-3-docker-client.md)
|
||||
- [ ] Phase 4: NPM Client [domain: backend] → [subplan](./phase-4-npm-client.md)
|
||||
- [ ] Phase 5: Registry Client & Poller [domain: backend] → [subplan](./phase-5-registry-poller.md)
|
||||
- [ ] Phase 6: Webhook Handler [domain: backend] → [subplan](./phase-6-webhook-handler.md)
|
||||
- [ ] Phase 7: Deployer & Health Checker [domain: backend] → [subplan](./phase-7-deployer.md)
|
||||
- [ ] Phase 8: REST API Layer [domain: backend] → [subplan](./phase-8-api-layer.md)
|
||||
- [ ] Phase 9: SvelteKit Dashboard & Project Views [domain: frontend] → [subplan](./phase-9-dashboard.md)
|
||||
- [ ] 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)
|
||||
- [ ] Phase 12: Hardening [domain: backend] → [subplan](./phase-12-hardening.md)
|
||||
|
||||
### Parallel Execution Notes
|
||||
- Phases 3 and 4 are independent (Docker client vs NPM client) — can run in parallel
|
||||
- Phases 9 and 10 are independent (dashboard vs settings pages) — can run in parallel
|
||||
|
||||
## Phase Progress Log
|
||||
|
||||
| Phase | Domain | Status | Review | Build | Committed |
|
||||
|-------|--------|--------|--------|-------|-----------|
|
||||
| Phase 1: Scaffold & Store | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 2: Crypto & Config | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 3: Docker Client | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 4: NPM Client | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 5: Registry & Poller | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 6: Webhook Handler | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 7: Deployer & Health | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 8: API Layer | backend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 9: Dashboard | frontend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 10: Settings & Deploy | frontend | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 11: Embed & SSE | fullstack | ⬜ Not Started | ⬜ | ⏭️ Skip (Big Bang) | ⬜ |
|
||||
| Phase 12: Hardening | backend | ⬜ Not Started | ⬜ | ✅ Required (Final) | ⬜ |
|
||||
|
||||
## Final Review
|
||||
- [ ] Comprehensive code review
|
||||
- [ ] Full build passes
|
||||
- [ ] Full test suite passes
|
||||
- [ ] Security review
|
||||
- [ ] Merged to `main`
|
||||
@@ -0,0 +1,57 @@
|
||||
# Phase 1: Project Scaffold & SQLite Store
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Initialize the Go project, establish the directory structure, and implement the SQLite store with schema, migrations, and CRUD operations for all entities.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Initialize Go module (`go mod init`), create directory structure per PLAN.md
|
||||
- [ ] Task 2: Add core dependencies to go.mod (sqlite, chi, yaml, uuid, cron)
|
||||
- [ ] Task 3: Define SQLite schema — tables for projects, stages, registries, settings, instances, deploys, deploy_logs
|
||||
- [ ] Task 4: Implement store initialization with auto-migration (create tables if not exist)
|
||||
- [ ] Task 5: Implement projects CRUD (Create, GetByID, GetAll, Update, Delete)
|
||||
- [ ] Task 6: Implement stages CRUD (Create, GetByProjectID, Update, Delete)
|
||||
- [ ] Task 7: Implement registries CRUD (Create, GetByID, GetAll, Update, Delete)
|
||||
- [ ] Task 8: Implement settings Get/Update (single-row config pattern)
|
||||
- [ ] Task 9: Implement instances CRUD (Create, GetByStageID, GetByID, Update, Delete, UpdateStatus)
|
||||
- [ ] Task 10: Implement deploys CRUD (Create, GetByProjectID, GetRecent, GetByID) + deploy_logs append
|
||||
- [ ] Task 11: Create `cmd/server/main.go` entry point (minimal — just opens DB, defers close)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `go.mod` — module definition and dependencies
|
||||
- `go.sum` — dependency checksums
|
||||
- `cmd/server/main.go` — entry point
|
||||
- `internal/store/store.go` — DB connection, schema, migrations
|
||||
- `internal/store/projects.go` — project queries
|
||||
- `internal/store/stages.go` — stage queries
|
||||
- `internal/store/registries.go` — registry queries
|
||||
- `internal/store/settings.go` — settings queries
|
||||
- `internal/store/instances.go` — instance queries
|
||||
- `internal/store/deploys.go` — deploy history queries
|
||||
|
||||
## Acceptance Criteria
|
||||
- `go mod tidy` succeeds
|
||||
- All store CRUD functions are implemented with proper error handling
|
||||
- Schema covers all entities from the architecture plan
|
||||
- Entry point compiles (may not fully run until later phases wire everything)
|
||||
|
||||
## Notes
|
||||
- Use `modernc.org/sqlite` for CGo-free SQLite
|
||||
- Use `go-chi/chi/v5` for routing (will be wired in Phase 8)
|
||||
- Settings table uses a single-row pattern (one row, upsert on update)
|
||||
- Instance status should be an enum-like string: "running", "stopped", "failed", "removing"
|
||||
- Deploy status: "pending", "pulling", "starting", "configuring_proxy", "health_checking", "success", "failed", "rolled_back"
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Code follows Go conventions (gofmt, proper error returns)
|
||||
- [ ] No unintended side effects
|
||||
- [ ] Schema is normalized and covers all planned entities
|
||||
- [ ] CRUD functions handle not-found cases properly
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,56 @@
|
||||
# Phase 10: Quick Deploy & Settings Pages
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
Build the Quick Deploy page (paste image, auto-inspect, one-click deploy) and all Settings pages (registries, credentials, global settings, webhook URL).
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Quick Deploy page (`routes/deploy/+page.svelte`) — image URL input, inspect button
|
||||
- [ ] Task 2: Quick Deploy inspect flow — call /api/deploy/inspect, display auto-filled form (project name, port, stage, subdomain)
|
||||
- [ ] Task 3: Quick Deploy submit — user reviews defaults, clicks Deploy, calls /api/deploy/quick
|
||||
- [ ] Task 4: Settings layout (`routes/settings/+layout.svelte`) — sub-navigation for settings sections
|
||||
- [ ] Task 5: Global settings page (`routes/settings/+page.svelte`) — domain, server IP, network, subdomain pattern, polling interval
|
||||
- [ ] Task 6: Registries page (`routes/settings/registries/+page.svelte`) — list, add, edit, delete, test connection
|
||||
- [ ] Task 7: Credentials page (`routes/settings/credentials/+page.svelte`) — NPM credentials, registry tokens (masked display)
|
||||
- [ ] Task 8: Webhook URL display and regenerate button in settings
|
||||
- [ ] Task 9: Projects config page (`routes/projects/config/+page.svelte` or integrated into project detail) — add/edit/delete projects, configure stages
|
||||
- [ ] Task 10: Stage configuration form — tag patterns, auto_deploy toggle, max_instances, subdomain override
|
||||
- [ ] Task 11: Form validation on all input pages — required fields, URL format, port range
|
||||
- [ ] Task 12: Success/error toast notifications for all form submissions
|
||||
|
||||
## Files to Modify/Create
|
||||
- `web/src/routes/deploy/+page.svelte` — quick deploy
|
||||
- `web/src/routes/settings/+layout.svelte` — settings layout
|
||||
- `web/src/routes/settings/+page.svelte` — global settings
|
||||
- `web/src/routes/settings/registries/+page.svelte` — registry management
|
||||
- `web/src/routes/settings/credentials/+page.svelte` — credential management
|
||||
- `web/src/lib/components/Toast.svelte` — toast notifications
|
||||
- `web/src/lib/components/FormField.svelte` — reusable form field with validation
|
||||
|
||||
## Acceptance Criteria
|
||||
- Quick Deploy: paste image URL → inspect → review defaults → deploy works end-to-end
|
||||
- All settings are editable and saved via API
|
||||
- Registry test connection shows success/failure
|
||||
- Credentials are masked in display (`••••••••`)
|
||||
- Webhook URL is shown with copy button and regenerate option
|
||||
- Form validation prevents bad submissions
|
||||
|
||||
## Notes
|
||||
- Quick Deploy is the zero-config entry point — should be dead simple UX
|
||||
- Credential fields: show mask, edit replaces entirely (no partial edit)
|
||||
- Registry test: calls POST /api/registries/:id/test, shows connection result
|
||||
- Toast component: appears top-right, auto-dismiss after 5s, color-coded (green/red)
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Quick deploy flow is intuitive (minimal clicks)
|
||||
- [ ] Credentials never shown in plaintext in UI
|
||||
- [ ] Form validation covers required fields and formats
|
||||
- [ ] Error states are handled with user-friendly messages
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Phase 11: Frontend Embed & Real-Time Updates
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
## Objective
|
||||
Build SvelteKit to static files, embed into the Go binary with `go:embed`, serve from Go, and implement SSE for real-time deploy progress and instance status updates.
|
||||
|
||||
## 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
|
||||
|
||||
## Files to Modify/Create
|
||||
- `web/svelte.config.js` — ensure static adapter outputs to `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
|
||||
- `Makefile` — build frontend + backend
|
||||
- `cmd/server/main.go` — wire embedded static serving and event bus
|
||||
|
||||
## Acceptance Criteria
|
||||
- `make build` produces a single Go binary with embedded frontend
|
||||
- Go binary serves the SvelteKit SPA on all non-API routes
|
||||
- Deploy progress streams in real-time via SSE
|
||||
- Instance status updates appear without page refresh
|
||||
- SSE reconnects automatically after network hiccups
|
||||
|
||||
## Notes
|
||||
- `go:embed` requires the embedded directory to be relative to the Go source file
|
||||
- SPA fallback: any request that doesn't match `/api/*` gets `index.html`
|
||||
- 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
|
||||
|
||||
## 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)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,59 @@
|
||||
# Phase 12: Hardening
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Production hardening — blue-green deploys, promote flow, dashboard auth, graceful shutdown, structured logging, and config export.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] 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
|
||||
|
||||
## 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/config/export.go` — config export to YAML
|
||||
- `internal/logging/logger.go` — structured JSON logger
|
||||
- `cmd/server/main.go` — graceful shutdown, structured logging init
|
||||
- `Dockerfile` — multi-stage build
|
||||
- `docker-compose.yml` — production compose file
|
||||
|
||||
## Acceptance Criteria
|
||||
- Blue-green: zero downtime during deploy (old container serves until new one is healthy)
|
||||
- Promote: production deploy only accepts tags from the specified source stage
|
||||
- Auth: unauthenticated requests to /api/* (except webhook) return 401
|
||||
- Graceful shutdown: in-progress deploys complete before exit
|
||||
- Logs are JSON-formatted with contextual fields
|
||||
- Config export produces valid YAML that could be re-imported
|
||||
- Docker image builds and runs correctly
|
||||
|
||||
## 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)
|
||||
- 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
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Blue-green deploy handles rollback if new container fails
|
||||
- [ ] Auth doesn't block webhook endpoint
|
||||
- [ ] Graceful shutdown tested with concurrent deploys
|
||||
- [ ] Dockerfile produces a minimal image
|
||||
- [ ] docker-compose.yml matches the example in PLAN.md
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- This is the final phase — no handoff needed. -->
|
||||
@@ -0,0 +1,50 @@
|
||||
# Phase 2: Crypto & Config Seed Loader
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Implement AES-256 encryption for credential storage and the YAML seed config parser that imports into SQLite on first launch.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Implement AES-256-GCM encrypt/decrypt functions using Go stdlib `crypto/aes` + `crypto/cipher`
|
||||
- [ ] Task 2: Key derivation from ENCRYPTION_KEY env var (SHA-256 hash to get 32 bytes)
|
||||
- [ ] Task 3: Define YAML config structs matching the seed format from PLAN.md
|
||||
- [ ] Task 4: Implement YAML parser — read and validate seed file
|
||||
- [ ] Task 5: Implement seed importer — checks if DB is empty, if so imports YAML into SQLite via store CRUD
|
||||
- [ ] Task 6: Encrypt credential fields (registry tokens, NPM password) during import
|
||||
- [ ] Task 7: Create `docker-watcher.example.yaml` with documented example config
|
||||
- [ ] Task 8: Wire seed import into `cmd/server/main.go` startup sequence
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/crypto/crypto.go` — AES-256-GCM encrypt/decrypt
|
||||
- `internal/config/config.go` — YAML structs and parser
|
||||
- `internal/config/seed.go` — seed import logic (YAML → SQLite)
|
||||
- `docker-watcher.example.yaml` — example seed config
|
||||
- `cmd/server/main.go` — add seed import to startup
|
||||
|
||||
## Acceptance Criteria
|
||||
- Encrypt then decrypt round-trips correctly
|
||||
- Different plaintexts produce different ciphertexts (random nonce)
|
||||
- YAML parsing handles all fields from the seed format
|
||||
- Seed import creates projects, stages, registries, and settings in SQLite
|
||||
- Credentials are encrypted before storage
|
||||
- Import is idempotent — skipped if DB already has data
|
||||
|
||||
## Notes
|
||||
- ENCRYPTION_KEY is the only secret env var — everything else is encrypted in SQLite
|
||||
- Use GCM mode for authenticated encryption (integrity + confidentiality)
|
||||
- Seed import should be transactional — all or nothing
|
||||
- The example YAML should have placeholder values, not real credentials
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Crypto uses secure practices (random nonce, GCM, no ECB)
|
||||
- [ ] No hardcoded keys or secrets
|
||||
- [ ] YAML parsing validates required fields
|
||||
- [ ] Import is transactional
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,52 @@
|
||||
# Phase 3: Docker Client
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Implement the Docker Engine API wrapper for container lifecycle management — pull images, inspect, create/start/stop/remove containers, and manage networks.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Create Docker client wrapper with socket connection (`/var/run/docker.sock`)
|
||||
- [ ] Task 2: Implement `PullImage(ctx, image, tag, authConfig)` — pull with optional registry auth
|
||||
- [ ] Task 3: Implement `InspectImage(ctx, image)` — extract EXPOSE ports, HEALTHCHECK, labels
|
||||
- [ ] Task 4: Implement `CreateContainer(ctx, config)` — create with name, image, env, ports, network, labels
|
||||
- [ ] Task 5: Implement `StartContainer(ctx, containerID)`, `StopContainer(ctx, containerID, timeout)`, `RemoveContainer(ctx, containerID, force)`
|
||||
- [ ] Task 6: Implement `RestartContainer(ctx, containerID, timeout)`
|
||||
- [ ] Task 7: Implement `ListContainers(ctx, filters)` — filter by labels to find managed containers
|
||||
- [ ] Task 8: Implement `EnsureNetwork(ctx, networkName)` — create network if not exists
|
||||
- [ ] Task 9: Implement `ConnectNetwork(ctx, networkID, containerID)` — attach container to network
|
||||
- [ ] Task 10: Add docker-watcher labels to all managed containers (`docker-watcher.project`, `docker-watcher.stage`, `docker-watcher.instance-id`)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/docker/client.go` — Docker client wrapper, connection setup
|
||||
- `internal/docker/container.go` — container lifecycle operations
|
||||
- `internal/docker/image.go` — pull and inspect operations
|
||||
- `internal/docker/network.go` — network management
|
||||
|
||||
## Acceptance Criteria
|
||||
- Client connects to Docker socket
|
||||
- Pull handles both public and authenticated registries
|
||||
- Image inspection extracts port, healthcheck, and label metadata
|
||||
- Container creation applies all config (env, ports, network, labels)
|
||||
- All operations return meaningful errors
|
||||
- Managed containers are identifiable via labels
|
||||
|
||||
## Notes
|
||||
- Use `github.com/docker/docker/client` SDK
|
||||
- Container names should be deterministic: `dw-{project}-{stage}-{tag-sanitized}`
|
||||
- All containers should be on the shared network (e.g., `staging-net`)
|
||||
- Port mapping: container's EXPOSE port → random host port (Docker auto-assigns)
|
||||
- Auth config for private registries will come from the store (encrypted tokens)
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Proper context propagation for cancellation
|
||||
- [ ] Resource cleanup (close client, remove failed containers)
|
||||
- [ ] No hardcoded values
|
||||
- [ ] Error messages include container/image identifiers
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Phase 4: NPM Client
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Implement the Nginx Proxy Manager API client — JWT authentication, CRUD for proxy hosts, and host lookup.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Create NPM client struct with base URL, cached JWT token, and auto-refresh
|
||||
- [ ] Task 2: Implement `Authenticate(ctx, email, password)` — POST /api/tokens, store JWT
|
||||
- [ ] Task 3: Implement `CreateProxyHost(ctx, config)` — POST /api/nginx/proxy-hosts
|
||||
- [ ] Task 4: Implement `UpdateProxyHost(ctx, id, config)` — PUT /api/nginx/proxy-hosts/{id}
|
||||
- [ ] Task 5: Implement `DeleteProxyHost(ctx, id)` — DELETE /api/nginx/proxy-hosts/{id}
|
||||
- [ ] Task 6: Implement `ListProxyHosts(ctx)` — GET /api/nginx/proxy-hosts
|
||||
- [ ] Task 7: Implement `FindProxyHostByDomain(ctx, domain)` — search existing hosts by domain name
|
||||
- [ ] Task 8: Define proxy host config struct (domain, forward host/port, SSL settings, etc.)
|
||||
- [ ] Task 9: Handle JWT token expiry — re-authenticate automatically on 401
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/npm/client.go` — NPM API client, auth, HTTP helpers
|
||||
- `internal/npm/types.go` — request/response types for proxy hosts
|
||||
|
||||
## Acceptance Criteria
|
||||
- Client authenticates and caches JWT
|
||||
- CRUD operations work for proxy hosts
|
||||
- Token refresh happens transparently on expiry
|
||||
- Proxy host config supports: domain, forward host, forward port, SSL (Let's Encrypt optional)
|
||||
- FindByDomain enables checking if a proxy already exists before creating
|
||||
|
||||
## Notes
|
||||
- NPM API base: typically `http://npm:81/api`
|
||||
- Forward host for containers: use container name on the shared Docker network
|
||||
- Forward port: the container's internal port (from EXPOSE)
|
||||
- SSL: for staging, can be disabled; production may want Let's Encrypt
|
||||
- NPM credentials come from settings (encrypted in SQLite, decrypted at runtime)
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] JWT caching and refresh work correctly
|
||||
- [ ] HTTP errors are properly handled (not just status code, but response body)
|
||||
- [ ] No credentials logged or leaked in errors
|
||||
- [ ] Struct types match NPM API contract
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,49 @@
|
||||
# Phase 5: Registry Client & Poller
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Implement the registry client interface with Gitea implementation, and the periodic tag polling scheduler.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Define `Registry` interface — `ListTags(ctx, image)`, `GetLatestTag(ctx, image, pattern)`
|
||||
- [ ] Task 2: Implement Gitea registry client — uses Gitea API to list container image tags
|
||||
- [ ] Task 3: Implement tag pattern matching — match tags against glob patterns (e.g., `dev-*`, `v*`)
|
||||
- [ ] Task 4: Implement tag comparison — detect new tags since last poll (store last-seen tag per project/stage)
|
||||
- [ ] Task 5: Create poller service — periodic scheduler using `robfig/cron`
|
||||
- [ ] Task 6: Poller logic — for each project/stage with polling enabled, check for new tags, trigger deploy if auto_deploy
|
||||
- [ ] Task 7: Add `last_polled_tag` field to instances or a new `poll_state` table in store
|
||||
- [ ] Task 8: Implement registry factory — create client based on registry type (gitea, future: github, dockerhub)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/registry/registry.go` — interface definition + factory
|
||||
- `internal/registry/gitea.go` — Gitea registry implementation
|
||||
- `internal/registry/poller.go` — polling scheduler service
|
||||
- `internal/store/poll_state.go` — poll state persistence (optional, or extend existing tables)
|
||||
|
||||
## Acceptance Criteria
|
||||
- Gitea client can list tags for a given image
|
||||
- Tag pattern matching correctly filters tags (glob-style)
|
||||
- Poller runs on configurable interval
|
||||
- New tags are detected by comparing against stored state
|
||||
- Registry factory returns correct client based on type
|
||||
|
||||
## Notes
|
||||
- Gitea API: `GET /api/v1/packages/{owner}/container/{image}/tags` (or similar, verify against Gitea docs)
|
||||
- Auth: Bearer token from registry config
|
||||
- Polling interval comes from global settings
|
||||
- The poller is a fallback — webhooks are the primary detection mechanism (Phase 6)
|
||||
- GitHub Container Registry support is future work — just define the interface now
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Interface is clean and minimal
|
||||
- [ ] Pattern matching handles edge cases (empty pattern, no tags)
|
||||
- [ ] Poller doesn't leak goroutines
|
||||
- [ ] Registry auth tokens handled securely
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,48 @@
|
||||
# Phase 6: Webhook Handler
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Implement the secret UUID-based webhook endpoint that receives image push notifications from CI systems, with auto-creation of unknown projects.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Implement webhook HTTP handler — `POST /api/webhook/:secret-uuid`
|
||||
- [ ] Task 2: Validate incoming payload — extract image name and tag
|
||||
- [ ] Task 3: Look up project by image name in store — match against configured project images
|
||||
- [ ] Task 4: If known project: match tag to stage via tag patterns, determine if auto_deploy
|
||||
- [ ] Task 5: If unknown project: auto-create project with defaults from image inspection (EXPOSE port, labels)
|
||||
- [ ] Task 6: Generate and store webhook secret UUID in settings (on first launch)
|
||||
- [ ] Task 7: Implement webhook URL regeneration (new UUID, invalidates old one)
|
||||
- [ ] Task 8: Define webhook payload struct (`{"image": "registry/org/app:tag"}`)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/webhook/handler.go` — webhook HTTP handler + payload parsing
|
||||
- `internal/webhook/matcher.go` — project/stage matching logic
|
||||
- `internal/webhook/autocreate.go` — auto-create project from unknown image
|
||||
|
||||
## Acceptance Criteria
|
||||
- Valid webhook URL with correct UUID triggers processing
|
||||
- Invalid/missing UUID returns 404 (no information leak)
|
||||
- Known images are matched to projects and stages
|
||||
- Unknown images trigger auto-creation with sensible defaults
|
||||
- Webhook URL can be regenerated
|
||||
|
||||
## Notes
|
||||
- Webhook URL format: `POST /api/webhook/d8f2a1e9-...`
|
||||
- No authentication needed beyond the secret UUID
|
||||
- Auto-created projects use: image EXPOSE port, "dev" as default stage, auto_deploy: true
|
||||
- The webhook handler calls into the deployer (Phase 7) — for now, define the interface/callback
|
||||
- Keep the handler thin — it matches and delegates
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] No information leak on invalid UUIDs
|
||||
- [ ] Payload validation rejects malformed input
|
||||
- [ ] Auto-creation uses safe defaults
|
||||
- [ ] Handler is stateless (delegates to store/deployer)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,54 @@
|
||||
# Phase 7: Deployer & Health Checker
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Implement the core deployment orchestrator: pull → start container → configure NPM proxy → health check → success/rollback. Plus multi-instance support and notifications.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Define deployer service struct — depends on Docker client, NPM client, store, notifier
|
||||
- [ ] Task 2: Implement deploy flow: pull image → create container → start → connect to network → configure proxy → health check
|
||||
- [ ] Task 3: Implement subdomain generation per convention: `stage-{stage}-{project}` for default, `stage-{stage}-{project}-{tag}` for specific
|
||||
- [ ] Task 4: Sanitize tags for DNS (dots → dashes, lowercase, truncate)
|
||||
- [ ] Task 5: Implement health checker — HTTP GET to `http://container:{port}{healthcheck_path}` with retries and timeout
|
||||
- [ ] Task 6: Implement rollback on health check failure — remove new container, delete NPM proxy host, update instance status
|
||||
- [ ] Task 7: Implement multi-instance support — multiple tags of same project/stage can run simultaneously
|
||||
- [ ] Task 8: Implement max_instances enforcement — remove oldest instance when limit reached
|
||||
- [ ] Task 9: Implement notification webhook — POST to configured URL on deploy success/failure
|
||||
- [ ] Task 10: Create deploy history records in store (status, timestamps, logs)
|
||||
- [ ] Task 11: Implement deploy log streaming — append log entries during deploy for real-time visibility
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/deployer/deployer.go` — main deploy orchestrator
|
||||
- `internal/deployer/subdomain.go` — subdomain generation and DNS sanitization
|
||||
- `internal/deployer/rollback.go` — rollback logic
|
||||
- `internal/health/checker.go` — HTTP health checker with retries
|
||||
- `internal/notify/notifier.go` — webhook notification sender
|
||||
|
||||
## Acceptance Criteria
|
||||
- Full deploy flow works end-to-end (pull → proxy → health check)
|
||||
- Failed health checks trigger automatic rollback
|
||||
- Multi-instance: deploying a new tag doesn't stop existing instances
|
||||
- max_instances removes oldest instance when exceeded
|
||||
- Notifications fire on success and failure
|
||||
- Deploy history is recorded with status and timestamps
|
||||
|
||||
## Notes
|
||||
- Health check: 3 retries, 5s between retries, 10s timeout per attempt (configurable later)
|
||||
- Subdomain pattern comes from global settings
|
||||
- Notifications are fire-and-forget (don't block deploy on notification failure)
|
||||
- Deploy logs should be structured entries (timestamp + message) for SSE streaming later
|
||||
- The deployer is the central orchestrator — webhook handler and poller both call into it
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Rollback cleans up ALL resources (container, proxy, instance record)
|
||||
- [ ] No goroutine leaks
|
||||
- [ ] Error handling at every step of the deploy flow
|
||||
- [ ] Subdomain generation produces valid DNS names
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,59 @@
|
||||
# Phase 8: REST API Layer
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
## Objective
|
||||
Wire up all REST API endpoints using chi router, connecting the store, deployer, and other services to HTTP handlers.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Set up chi router with middleware (logging, recovery, CORS, JSON content-type)
|
||||
- [ ] Task 2: Implement project endpoints — GET/POST /api/projects, GET/PUT/DELETE /api/projects/:id
|
||||
- [ ] Task 3: Implement stage endpoints — POST /api/projects/:id/stages, PUT/DELETE /api/projects/:id/stages/:stage
|
||||
- [ ] Task 4: Implement instance endpoints — GET /api/projects/:id/stages/:stage/instances, POST (deploy), DELETE (remove)
|
||||
- [ ] Task 5: Implement instance control endpoints — POST .../instances/:iid/stop, start, restart
|
||||
- [ ] Task 6: Implement quick deploy endpoints — POST /api/deploy/inspect, POST /api/deploy/quick
|
||||
- [ ] Task 7: Implement registry endpoints — GET/POST /api/registries, PUT/DELETE /api/registries/:id, POST .../test
|
||||
- [ ] Task 8: Implement settings endpoints — GET/PUT /api/settings, GET /api/settings/webhook-url, POST .../regenerate
|
||||
- [ ] Task 9: Implement deploy history endpoints — GET /api/deploys, GET /api/deploys/:id/logs (SSE stub)
|
||||
- [ ] Task 10: Implement registry tags endpoint — GET /api/registries/:id/tags/:image
|
||||
- [ ] Task 11: Wire webhook handler into router — POST /api/webhook/:secret-uuid
|
||||
- [ ] Task 12: Wire everything in main.go — initialize all services, start HTTP server
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/api/router.go` — chi router setup, middleware
|
||||
- `internal/api/projects.go` — project CRUD handlers
|
||||
- `internal/api/stages.go` — stage CRUD handlers
|
||||
- `internal/api/instances.go` — instance lifecycle handlers
|
||||
- `internal/api/deploys.go` — deploy + quick deploy handlers
|
||||
- `internal/api/registries.go` — registry CRUD + test + tags handlers
|
||||
- `internal/api/settings.go` — settings handlers
|
||||
- `internal/api/middleware.go` — middleware (logging, CORS, recovery)
|
||||
- `internal/api/response.go` — consistent API response helpers (envelope format)
|
||||
- `cmd/server/main.go` — full service wiring and HTTP server start
|
||||
|
||||
## Acceptance Criteria
|
||||
- All endpoints from the API spec in PLAN.md are implemented
|
||||
- Consistent JSON envelope response format (success, data, error, metadata)
|
||||
- CORS configured for frontend dev (localhost origins)
|
||||
- Proper HTTP status codes (200, 201, 400, 404, 500)
|
||||
- main.go starts a fully wired HTTP server
|
||||
|
||||
## Notes
|
||||
- Response envelope: `{"success": bool, "data": any, "error": string|null, "meta": {pagination}}`
|
||||
- CORS: allow all origins in dev, restrict in production (configurable later)
|
||||
- SSE for deploy logs is a stub in this phase — real implementation in Phase 11
|
||||
- Quick deploy: /inspect pulls and inspects image, returns defaults; /quick creates project + deploys
|
||||
- All handlers should validate input and return 400 for bad requests
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] All API endpoints from PLAN.md are covered
|
||||
- [ ] Consistent response format across all endpoints
|
||||
- [ ] Input validation on all POST/PUT handlers
|
||||
- [ ] No business logic in handlers (delegates to services)
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
@@ -0,0 +1,64 @@
|
||||
# Phase 9: SvelteKit Dashboard & Project Views
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
## Objective
|
||||
Build the SvelteKit frontend with the dashboard overview and project detail views — project list, instance status, controls, and deploy history.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Initialize SvelteKit project in `web/` directory with TypeScript, static adapter
|
||||
- [ ] Task 2: Set up Tailwind CSS (or project's preferred styling)
|
||||
- [ ] Task 3: Create shared API client (`lib/api.ts`) — typed fetch wrapper for all backend endpoints
|
||||
- [ ] Task 4: Define TypeScript types (`lib/types.ts`) — Project, Stage, Instance, Deploy, Registry, Settings
|
||||
- [ ] Task 5: Create layout with navigation — sidebar or top nav with Dashboard, Projects, Deploy, Settings links
|
||||
- [ ] Task 6: Dashboard page (`routes/+page.svelte`) — project overview cards with instance counts, status indicators, latest activity
|
||||
- [ ] Task 7: Projects list page (`routes/projects/+page.svelte`) — all projects with quick stats, "Add Project" button
|
||||
- [ ] Task 8: Project detail page (`routes/projects/[id]/+page.svelte`) — stages, instances per stage, controls
|
||||
- [ ] Task 9: Instance controls — Stop, Start, Restart, Remove buttons with confirmation dialogs
|
||||
- [ ] Task 10: Deploy history section in project detail — recent deploys with status, timestamp, tag
|
||||
- [ ] Task 11: "Deploy new version" dropdown — list available tags from registry, trigger deploy
|
||||
- [ ] Task 12: Create reusable components: StatusBadge, InstanceCard, ProjectCard, ConfirmDialog
|
||||
|
||||
## Files to Modify/Create
|
||||
- `web/package.json` — SvelteKit project config
|
||||
- `web/svelte.config.js` — SvelteKit config with static adapter
|
||||
- `web/vite.config.ts` — Vite config with API proxy for dev
|
||||
- `web/src/app.html` — base HTML
|
||||
- `web/src/lib/api.ts` — API client
|
||||
- `web/src/lib/types.ts` — shared TypeScript types
|
||||
- `web/src/routes/+layout.svelte` — app layout with navigation
|
||||
- `web/src/routes/+page.svelte` — dashboard
|
||||
- `web/src/routes/projects/+page.svelte` — project list
|
||||
- `web/src/routes/projects/[id]/+page.svelte` — project detail
|
||||
- `web/src/lib/components/StatusBadge.svelte` — status indicator
|
||||
- `web/src/lib/components/InstanceCard.svelte` — instance display
|
||||
- `web/src/lib/components/ProjectCard.svelte` — project summary card
|
||||
- `web/src/lib/components/ConfirmDialog.svelte` — confirmation modal
|
||||
|
||||
## Acceptance Criteria
|
||||
- SvelteKit project builds to static output
|
||||
- Dashboard shows all projects with live status
|
||||
- Project detail shows stages, instances, and controls
|
||||
- Instance controls trigger correct API calls
|
||||
- Deploy dropdown fetches and displays available tags
|
||||
- UI is responsive and clean
|
||||
|
||||
## Notes
|
||||
- SvelteKit static adapter for embedding in Go binary
|
||||
- API proxy in vite.config.ts for dev: proxy `/api` to `http://localhost:8080`
|
||||
- Use SvelteKit's `fetch` for SSR-compatible data loading
|
||||
- Status colors: green=running, yellow=starting, red=failed, gray=stopped
|
||||
- Keep components small and reusable
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] TypeScript types match backend API response format
|
||||
- [ ] API client handles errors gracefully with user feedback
|
||||
- [ ] No hardcoded API URLs (use relative paths)
|
||||
- [ ] Components are reusable and well-structured
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
Reference in New Issue
Block a user