chore: remove feature plan files after merge

This commit is contained in:
2026-04-02 15:12:59 +03:00
parent c2289dc344
commit 1c37bb2ccf
8 changed files with 0 additions and 452 deletions
@@ -1,33 +0,0 @@
# Feature Context: Cloudflare DNS Management
## Configuration
- **Development mode:** Automated
- **Execution mode:** Direct
- **Strategy:** Big Bang
- **Build (Go):** `go build ./cmd/server`
- **Build (Frontend):** `cd web && npm run build`
- **Check (Frontend):** `cd web && npm run check`
- **Test:** `go test ./...`
- **Dev server:** `./scripts/dev-server.sh` (port 8090)
## Current State
Starting fresh — no implementation yet.
## Cross-Phase Dependencies
- Phase 2 depends on Phase 1 (settings fields for Cloudflare credentials)
- Phase 3 depends on Phase 2 (dns.Provider interface)
- Phase 4 depends on Phase 1 (API endpoints for settings)
- Phase 5 depends on Phase 2 + Phase 6 (client + sync logic)
- Phase 6 depends on Phase 2 (Cloudflare client) + Phase 3 (dns_records table)
## Key Architecture Decisions
- DNS provider abstraction via `internal/dns.Provider` interface
- Cloudflare API v4 via direct HTTP (no SDK) — keeps dependencies minimal
- Local `dns_records` table tracks managed records for reconciliation
- DNS operations are best-effort (log warnings, don't block deploys)
- A records only, pointing to `ServerIP` from settings
## Environment & Runtime Notes
- Encryption key from `ENCRYPTION_KEY` env var (AES-256-GCM)
- SQLite with WAL mode, auto-migration on startup
- Frontend is SvelteKit 2 + Svelte 5 + Tailwind CSS 4
-50
View File
@@ -1,50 +0,0 @@
# Feature: Cloudflare DNS Management
**Branch:** `feature/cloudflare-dns-management`
**Base branch:** `main`
**Created:** 2026-04-02
**Status:** 🟡 In Progress
**Strategy:** Big Bang
**Mode:** Automated
**Execution:** Direct
## Summary
Introduce flexible DNS management. By default, wildcard DNS is assumed (current behavior).
When disabled, the user selects a DNS provider (Cloudflare initially) and provides API
credentials. DNS A records are then automatically kept in sync with proxy consumers
(deployed instances and standalone proxies). A dedicated DNS Records page provides
visibility, filtering, and manual sync/reconciliation.
## Build & Test Commands
- **Build (Go):** `go build ./cmd/server`
- **Build (Frontend):** `cd web && npm run build`
- **Check (Frontend):** `cd web && npm run check`
- **Test (Go):** `go test ./...`
- **Dev server:** `./scripts/dev-server.sh`
## Phases
- [ ] Phase 1: Settings model & API [domain: backend] → [subplan](./phase-1-settings-model.md)
- [ ] Phase 2: Cloudflare DNS client [domain: backend] → [subplan](./phase-2-cloudflare-client.md)
- [ ] Phase 3: DNS lifecycle hooks [domain: backend] → [subplan](./phase-3-dns-hooks.md)
- [ ] Phase 4: Settings UI — DNS configuration [domain: frontend] → [subplan](./phase-4-settings-ui.md)
- [ ] Phase 5: DNS Records page [domain: fullstack] → [subplan](./phase-5-dns-records-page.md)
- [ ] Phase 6: DNS sync & reconciliation [domain: backend] → [subplan](./phase-6-dns-sync.md)
## Phase Progress Log
| Phase | Domain | Status | Review | Build | Committed |
|-------|--------|--------|--------|-------|-----------|
| Phase 1: Settings model & API | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 2: Cloudflare DNS client | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 3: DNS lifecycle hooks | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 4: Settings UI — DNS config | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 5: DNS Records page | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 6: DNS sync & reconciliation | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
## Final Review
- [ ] Comprehensive code review
- [ ] Full build passes
- [ ] Full test suite passes
- [ ] Merged to `main`
@@ -1,59 +0,0 @@
# Phase 1: Settings Model & API
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Extend the Settings model and API to support DNS provider configuration.
## Tasks
- [ ] Task 1: Add new fields to `Settings` struct in `internal/store/models.go`
- `WildcardDNS` (bool, default true)
- `DNSProvider` (string, default "")
- `CloudflareAPIToken` (string, encrypted)
- `CloudflareZoneID` (string)
- [ ] Task 2: Add migration columns in `internal/store/store.go`
- `wildcard_dns` INTEGER DEFAULT 1
- `dns_provider` TEXT DEFAULT ''
- `cloudflare_api_token` TEXT DEFAULT ''
- `cloudflare_zone_id` TEXT DEFAULT ''
- [ ] Task 3: Update `GetSettings()` and `UpdateSettings()` in `internal/store/settings.go`
- Read/write new fields
- Encrypt/decrypt `cloudflare_api_token`
- [ ] Task 4: Update `GET /api/settings` handler to include new fields (mask token)
- [ ] Task 5: Update `PUT /api/settings` handler to accept new fields
- [ ] Task 6: Add `POST /api/settings/dns/test` endpoint — validate Cloudflare token + zone
- [ ] Task 7: Add `GET /api/settings/dns/zones` endpoint — list Cloudflare zones for picker
- [ ] Task 8: Register new routes in `internal/api/router.go`
## Files to Modify/Create
- `internal/store/models.go` — add fields to Settings struct
- `internal/store/store.go` — add migration columns
- `internal/store/settings.go` — update read/write queries
- `internal/api/settings.go` — update handlers, add new endpoints
- `internal/api/router.go` — register new routes
## Acceptance Criteria
- New settings fields are persisted and retrievable
- Cloudflare API token is encrypted at rest
- GET /api/settings returns new fields (token masked)
- PUT /api/settings accepts and stores new fields
- DNS test and zones endpoints registered (can return placeholder until Phase 2)
## Notes
- Token encryption uses existing `crypto.Encrypt/Decrypt`
- `has_cloudflare_api_token` bool in GET response (same pattern as npm_password)
- DNS test/zones endpoints will make real Cloudflare API calls — Phase 2 client needed
for full implementation, but can use inline HTTP calls for these two endpoints
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,61 +0,0 @@
# Phase 2: Cloudflare DNS Client
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Create an `internal/dns` package with a `Provider` interface and a Cloudflare implementation
using the Cloudflare API v4 (direct HTTP, no SDK).
## Tasks
- [ ] Task 1: Define `Provider` interface in `internal/dns/provider.go`
- `EnsureRecord(ctx, fqdn, ip) error` — create or update A record
- `DeleteRecord(ctx, fqdn) error` — delete A record if exists
- `ListRecords(ctx) ([]Record, error)` — list all A records in the zone
- `Record` struct: ID, FQDN, Type, Content (IP), Proxied, TTL
- [ ] Task 2: Create `internal/dns/cloudflare.go` — Cloudflare implementation
- HTTP client with `Authorization: Bearer <token>` header
- Base URL: `https://api.cloudflare.com/client/v4`
- `EnsureRecord`: GET records by name, create if missing, update if IP differs
- `DeleteRecord`: GET record by name, DELETE if found
- `ListRecords`: GET all A records in zone
- `ListZones`: GET zones for the token (for zone picker)
- `TestConnection`: verify token works (GET /user/tokens/verify)
- [ ] Task 3: Create `internal/dns/dns.go` — factory function
- `NewProvider(providerName, config) (Provider, error)`
- Config struct with token, zoneID
- Returns `nil, nil` when providerName is empty (wildcard mode)
- [ ] Task 4: Wire DNS test/zones endpoints in `internal/api/settings.go`
- `POST /api/settings/dns/test` — create temp Cloudflare client, call TestConnection
- `GET /api/settings/dns/zones` — create temp client, call ListZones
## Files to Modify/Create
- `internal/dns/provider.go` — interface + Record type
- `internal/dns/cloudflare.go` — Cloudflare implementation
- `internal/dns/dns.go` — factory function
- `internal/api/settings.go` — wire test/zones endpoints to real client
## Acceptance Criteria
- Provider interface defined with EnsureRecord, DeleteRecord, ListRecords
- Cloudflare client makes correct API calls with proper auth headers
- EnsureRecord is idempotent (create if missing, update if changed, no-op if same)
- DeleteRecord is idempotent (no error if record doesn't exist)
- ListZones returns zone ID + name pairs
- TestConnection returns success/failure
## Notes
- Cloudflare API v4 docs: zones endpoint, dns_records endpoint
- Use `context.Context` for timeout control on all HTTP calls
- A records only (type "A"), TTL=1 (auto), proxied=false (DNS only, not CF proxy)
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,67 +0,0 @@
# Phase 3: DNS Lifecycle Hooks
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Hook DNS record management into the deployer and standalone proxy manager so that DNS
records are automatically created/updated/deleted in sync with proxy consumers.
## Tasks
- [ ] Task 1: Create `dns_records` table for tracking managed records
- Columns: id, fqdn, provider_record_id, consumer_type (instance/standalone), consumer_id, created_at, updated_at
- Store queries: CreateDNSRecord, DeleteDNSRecord, GetDNSRecordByFQDN, ListDNSRecords, GetDNSRecordsByConsumer
- [ ] Task 2: Add DNS provider to `Deployer` struct
- Accept `dns.Provider` in constructor (can be nil for wildcard mode)
- Helper: `ensureDNS(ctx, fqdn, deployID)` — calls provider.EnsureRecord + saves to dns_records
- Helper: `removeDNS(ctx, fqdn, deployID)` — calls provider.DeleteRecord + removes from dns_records
- [ ] Task 3: Hook into deployer — instance creation
- After `configureProxy` succeeds in `deployer.go` and `bluegreen.go` → call `ensureDNS`
- FQDN = `subdomain + "." + settings.Domain`
- [ ] Task 4: Hook into deployer — instance removal
- In `removeInstance` after NPM proxy deletion → call `removeDNS`
- In `rollback` after NPM proxy deletion → call `removeDNS`
- [ ] Task 5: Hook into standalone proxy manager
- `CreateProxy` → after NPM host created, call `ensureDNS`
- `UpdateProxy` → if domain changed, `removeDNS(old)` + `ensureDNS(new)`
- `DeleteProxy` → call `removeDNS`
- [ ] Task 6: Wire DNS provider into main.go
- Read settings on startup, create provider if non-wildcard
- Pass provider to Deployer and proxy Manager constructors
- Handle provider being nil (wildcard mode = no DNS ops)
- [ ] Task 7: Add `DNSRecord` model to `internal/store/models.go`
## Files to Modify/Create
- `internal/store/models.go` — add DNSRecord struct
- `internal/store/store.go` — add dns_records table migration
- `internal/store/dns_records.go` — CRUD queries
- `internal/deployer/deployer.go` — add DNS hooks
- `internal/deployer/bluegreen.go` — add DNS hooks
- `internal/deployer/rollback.go` — add DNS cleanup
- `internal/proxy/manager.go` — add DNS hooks
- `cmd/server/main.go` — wire DNS provider
## Acceptance Criteria
- DNS records created when proxy consumers are created (if non-wildcard mode)
- DNS records deleted when proxy consumers are removed
- DNS records updated when standalone proxy domain changes
- All DNS operations are best-effort (log warning on failure, don't block)
- dns_records table tracks all managed records
- Wildcard mode (default) skips all DNS operations
## Notes
- DNS operations must be wrapped in error handling that logs but doesn't fail the deploy
- The dns_records table is the local source of truth for reconciliation (Phase 6)
- Provider can be nil — all hooks must check for nil before calling
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,55 +0,0 @@
# Phase 4: Settings UI — DNS Configuration
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
## Objective
Add a "DNS Configuration" section to the Settings page with wildcard toggle, provider
selection, Cloudflare credential fields, and connection test.
## Tasks
- [ ] Task 1: Add new API functions in `web/src/lib/api.ts`
- `testDnsConnection(token, zoneId)` → POST /api/settings/dns/test
- `listDnsZones(token)` → GET /api/settings/dns/zones
- [ ] Task 2: Add i18n keys for DNS settings in locale files
- [ ] Task 3: Add DNS Configuration section to `web/src/routes/settings/+page.svelte`
- Toggle: "Wildcard DNS is configured" (checkbox/switch)
- When unchecked, show:
- DNS Provider dropdown (only "Cloudflare" option)
- API Token field (password type, show `has_cloudflare_api_token` indicator)
- Zone picker (loaded from API after token provided)
- "Test Connection" button with success/error feedback
- All DNS fields hidden when wildcard is checked
- [ ] Task 4: Wire save logic — include new fields in `handleSave`
- [ ] Task 5: Wire load logic — populate DNS fields from settings response
## Files to Modify/Create
- `web/src/lib/api.ts` — add DNS API functions
- `web/src/routes/settings/+page.svelte` — add DNS config section
- `web/src/lib/i18n/en.ts` (or equivalent locale file) — add DNS translation keys
## Acceptance Criteria
- Wildcard toggle visible and functional (default: checked)
- Unchecking reveals Cloudflare configuration form
- API token field uses password masking
- Zone picker loads zones from Cloudflare API
- Test Connection button shows success/failure
- Settings save includes DNS fields
- Settings load populates DNS fields
## Notes
- Follow existing settings page patterns (FormField, EntityPicker for zones)
- Zone picker similar to SSL certificate picker pattern
- Token field similar to NPM password field (has_token indicator)
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,65 +0,0 @@
# Phase 5: DNS Records Page
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** fullstack
## Objective
Create a dedicated DNS Records page that lists all managed DNS records with filtering,
consumer mapping, and sync status visibility.
## Tasks
- [ ] Task 1: Add backend endpoint `GET /api/dns/records`
- Returns merged view: local dns_records + Cloudflare actual records
- Each record: fqdn, type, value (IP), consumer_type, consumer_name, status (synced/orphaned/missing)
- Orphaned = exists in Cloudflare but no local consumer
- Missing = local consumer exists but no Cloudflare record
- [ ] Task 2: Add API handler in `internal/api/dns.go`
- New handler file for DNS-related endpoints
- Register routes in router.go
- [ ] Task 3: Add frontend API function `getDnsRecords()` in `api.ts`
- [ ] Task 4: Create DNS Records page at `web/src/routes/dns/+page.svelte`
- Table with columns: FQDN, Type, Value, Consumer, Status
- Consumer column shows: instance name (project/stage) or standalone proxy name
- Status badges: synced (green), orphaned (yellow), missing (red)
- Search filter (by FQDN substring)
- Filter by consumer type: all / managed / standalone
- Filter by status: all / synced / orphaned / missing
- Manual sync button (calls POST /api/dns/sync — Phase 6)
- Refresh button to re-fetch from Cloudflare
- [ ] Task 5: Add navigation link to DNS page
- Only visible when wildcard DNS is disabled
- Add to sidebar/nav under Settings or as top-level
- [ ] Task 6: Add i18n keys for DNS records page
## Files to Modify/Create
- `internal/api/dns.go` — new handler file
- `internal/api/router.go` — register DNS routes
- `web/src/lib/api.ts` — add DNS records API function
- `web/src/routes/dns/+page.svelte` — new page
- `web/src/routes/dns/+page.ts` — optional load function
- Navigation component — add DNS link
- Locale files — add i18n keys
## Acceptance Criteria
- DNS Records page accessible at /dns
- Table shows all records with correct status
- Filtering works: search text, consumer type, sync status
- Only accessible/visible when wildcard DNS is disabled
- Consumer names resolve correctly (project/stage for managed, proxy name for standalone)
## Notes
- Status computation: compare local dns_records table with Cloudflare ListRecords response
- Cache Cloudflare response for a few seconds to avoid rate limiting on page load
- Navigation link visibility tied to settings (may need a store or settings check)
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->
@@ -1,62 +0,0 @@
# Phase 6: DNS Sync & Reconciliation
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Implement reconciliation logic that compares expected DNS records (from active consumers)
with actual Cloudflare records, and provides a sync endpoint to fix discrepancies.
## Tasks
- [ ] Task 1: Add `POST /api/dns/sync` endpoint
- Computes expected records from: active instances with proxy + standalone proxies
- Fetches actual records from Cloudflare via ListRecords
- Creates missing records (consumer exists, no CF record)
- Deletes orphaned records (CF record exists, no consumer) — only for records in dns_records table
- Updates dns_records table to reflect current state
- Returns sync report: created N, deleted N, already_synced N
- [ ] Task 2: Add helper to compute expected records
- Query all instances where npm_proxy_id > 0 and status = "running" → extract FQDN
- Query all standalone proxies → extract domain
- Return list of expected FQDNs
- [ ] Task 3: Add `DELETE /api/dns/records/{fqdn}` endpoint
- Manual deletion of a specific DNS record (for orphan cleanup)
- Calls provider.DeleteRecord + removes from dns_records
- [ ] Task 4: Wire sync endpoint in `internal/api/dns.go` and router
- [ ] Task 5: Add frontend sync button handler in DNS Records page
- Call POST /api/dns/sync
- Show sync report (toast or inline)
- Refresh records list after sync
## Files to Modify/Create
- `internal/api/dns.go` — add sync + delete endpoints
- `internal/api/router.go` — register new routes
- `internal/store/dns_records.go` — add helper queries (list consumers with FQDNs)
- `web/src/lib/api.ts` — add syncDnsRecords(), deleteDnsRecord() functions
- `web/src/routes/dns/+page.svelte` — wire sync button
## Acceptance Criteria
- POST /api/dns/sync creates missing and removes orphaned records
- Sync report returned with counts
- Manual delete endpoint works for individual records
- Frontend sync button triggers reconciliation and refreshes view
- Only records tracked in dns_records table are candidates for orphan deletion
(don't delete unrelated Cloudflare records)
## Notes
- Safety: only delete Cloudflare records that are tracked in our dns_records table
(never touch records we didn't create)
- Rate limiting: Cloudflare API has rate limits, batch operations where possible
- Expected records query needs to join instances + standalone_proxies with settings.domain
## Review Checklist
- [ ] All tasks completed
- [ ] Code follows project conventions
- [ ] No unintended side effects
- [ ] Build passes
- [ ] Tests pass (new + existing)
## Handoff to Next Phase
<!-- Filled in after completion -->