14 KiB
Post-Deploy Fixes + Config Delivery Plan
TL;DR
Quick Summary: Fix 3 immediate bugs (DNS clear, peer detection debug, peer IP change) and design secure config delivery for devices.
Deliverables:
- DNS override can be cleared to empty (frontend fix)
- Debug procedure documented for peer detection failures
- InternalIP editable from DeviceDetail.vue
- Firewall port range can be empty (apply to all ports)
- Encrypted config delivery endpoint + companion plaintext endpoint
Estimated Effort: Medium Parallel Execution: YES - 2 waves Critical Path: T1 → [T2] → T3
Context
Original Issues
-
DNS tidak bisa dikosongkan: Ketika user kosongkan field DNS Override di DeviceDetail.vue lalu save,
advForm.value.dnsjadi"", tapi"" || undefineddi JavaScript menghasilkanundefined. Akibatnya fielddnstidak terkirim ke API, nilai DB tidak berubah. -
Peer tidak mendeteksi koneksi: Device official WG client tetap Offline meski terkoneksi. Perlu debugging multi-layer: (1) cek collector jalan, (2) cek kernel handshake, (3) cek frontend isDeviceOnline().
-
Tidak bisa ganti peer IP:
UpdateDeviceRequesttidak memiliki fieldInternalIP. IP hanya dialokasikan sekali oleh IPAM saat provisioning. -
Config delivery: Ingin endpoint aman untuk device ambil config, tapi juga bisa debug plaintext untuk WireGuard client manual.
Current State
- DNS backend:
devices.go:236-238— sudah handlereq.DNS != nil, termasuk empty string ✅ - DNS frontend:
DeviceDetail.vue:300—dns: advForm.value.dns || undefined❌ bug - Peer IP:
UpdateDeviceRequestdidevices.go:190— TIDAK ada fieldInternalIP - Config endpoints:
GET /devices/:id/config→ plaintext (admin only)POST /provision→encrypted_configAES-256-GCM (token-based)GET /devices/:id/config/qr→ QR image (admin only)- Tidak ada endpoint publik untuk device mengambil config sendiri
Work Objectives
Objective A: Fix Bugs (Issue 1)
Must Have:
- DNS override bisa dikosongkan dan tersimpan sebagai
""di DB npm run buildpasses
Must NOT Have:
- Tidak mengubah struktur API atau backend
Objective B: Debug Guide (Issue 2)
Must Have:
- Step-by-step debugging procedure untuk peer detection
- Commands untuk tiap layer (kernel, collector, DB, frontend)
Objective C: Peer IP Change (Issue 3)
Must Have:
- Admin bisa mengubah
internal_ipdevice via API - API memvalidasi format IP sebelum update
- API mengecek duplikasi IP di IPAM sebelum update
- Firewall rule re-sync jika IP berubah
- Frontend input field untuk InternalIP di DeviceDetail.vue
Must NOT Have:
- Jangan override alokasi IPAM untuk device yang belum provisioning
- Jangan izinkan non-admin mengganti IP
Objective D: Config Delivery (Issue 4)
Questions to resolve (akan ditanyakan ke user):
- Apakah device perlu endpoint autentikasi sendiri (token/API key) untuk ambil config?
- Atau cukup admin ambilkan seperti sekarang?
- Encrypted payload (AES-GCM) atau cukup HTTPS saja?
- Perlu QR code juga atau config text saja?
Must Have (asumsi awal):
- Endpoint untuk device mengambil config sendiri (setelah provisioning)
- Opsi plaintext untuk debugging
- Opsi encrypted payload untuk production
Verification Strategy
ZERO HUMAN INTERVENTION — ALL verification is agent-executed.
- Frontend:
npm run build+ Playwright (cek DNS save + IP input) - Backend:
go build -tags dev ./...+ curl API test - Config: curl endpoint + validasi response format
Execution Strategy
Wave 1 (Parallel — all remaining work):
├── T1: Fix DNS clear bug (frontend) [DONE]
├── T2: Write debugging docs [INFORMATIONAL]
├── T3: Add InternalIP change to backend + frontend [DONE]
├── T4: Fix firewall port range to allow empty (models + API + frontend + nftables) [DONE]
└── F1-F3: Build verification + code review
Wave 2 (Future — separate plan):
├── Device-Agent Registration + Approval flow
└── Encrypted config delivery endpoint
TODOs
-
1. Fix DNS clear bug
What to do:
- File:
apps/dashboard-ui/src/views/DeviceDetail.vuebaris 300 - Ubah
dns: advForm.value.dns || undefinedmenjadidns: advForm.value.dns === '' ? '' : advForm.value.dns || undefined - Run
npm run builduntuk verifikasi
Must NOT do:
- Jangan ubah backend — sudah support empty string
Parallelization: Wave 1, parallel with T2, T3
Acceptance Criteria:
npm run buildpasses- Set DNS → kosongkan → save → reload → DNS tetap kosong
QA Scenarios:
Scenario: Clear DNS override saves correctly Tool: Bash (curl) Preconditions: Device with existing DNS Steps: 1. curl -X PUT /api/v1/devices/$ID -H "Content-Type: application/json" -d '{"dns": ""}' -H "Authorization: Bearer $TOKEN" 2. curl -X GET /api/v1/devices/$ID -H "Authorization: Bearer $TOKEN" Expected Result: Response JSON includes "dns": "" or null Evidence: .sisyphus/evidence/postdeploy-t1-dns-clear.txt Scenario: Build passes Tool: Bash Steps: cd apps/dashboard-ui && npm run build Expected Result: vue-tsc + vite build succeeds Evidence: .sisyphus/evidence/postdeploy-t1-build.txtCommit: YES —
fix(ui): allow clearing DNS override to empty - File:
-
2. Document peer detection debugging procedure
What to do: Create debugging doc covering 5 layers:
- Kernel:
sudo wg show— peer handshake dari kernel - Collector:
docker logsgrep handshake — apakah collector jalan - Node type: query DB
wg_servers.name— local node atau remote - DB data:
psqlcekis_active,last_handshake,client_info - Status API:
curl /devices/:id/status— realtime status dari API
Parallelization: Wave 1, parallel
Acceptance Criteria:
- Setiap langkah punya command exact yang bisa di-copy-paste
- Setiap langkah punya expected output
Commit: NO (informational)
- Kernel:
-
3. Add InternalIP to UpdateDeviceRequest + handler + UI
What to do:
Backend —
apps/server-core/api/devices.go:- Tambah
InternalIP *string json:"internal_ip"keUpdateDeviceRequeststruct - Di Update handler, setelah blok DNS (line ~238), tambah:
if req.InternalIP != nil { ip := net.ParseIP(*req.InternalIP) if ip == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid internal_ip format"}) return } // Check duplicate IP across all devices + servers var count int64 h.db.Model(&models.Device{}).Where("internal_ip = ? AND id != ?", *req.InternalIP, id).Count(&count) if count > 0 { c.JSON(http.StatusConflict, gin.H{"error": "internal_ip already in use"}) return } updates["internal_ip"] = *req.InternalIP // Re-sync firewall if endpoint_allowed_ips also changed or existed if req.EndpointAllowedIPs != nil && *req.EndpointAllowedIPs != "" { destCIDR := *req.EndpointAllowedIPs // Remove old rule, add new rule h.fw.AddForwardRule(device.Name, ip, destCIDR) } }
Frontend —
apps/dashboard-ui/src/views/DeviceDetail.vue:- Di advanced settings section, tambah input field:
<div> <label class="block text-xs text-gray-500 mb-1">Internal IP (WireGuard)</label> <input v-model="advForm.internalIP" placeholder="10.172.21.x" class="w-full bg-black/50 border border-white/10 rounded p-2 text-white text-sm focus:border-cyan-500 focus:outline-none" /> </div> - Di
advFormref, tambahinternalIP: '' - Di
load(), tambahadvForm.value.internalIP = device.value.InternalIP || '' - Di
saveAdvanced(), tambahinternal_ip: advForm.value.internalIP || undefined
Must NOT do:
- Jangan izinkan non-admin mengganti IP
- Jangan override IP device yang belum provisioning (
!device.InternalIP == nil) - Jangan lupa re-sync firewall rule
Parallelization: Wave 1
Acceptance Criteria:
go build -tags dev ./...passesnpm run buildpasses- curl PUT dengan internal_ip → 200, IP berubah
- curl PUT dengan IP duplikat → 409 Conflict
- curl PUT dengan IP invalid → 400 Bad Request
- IP baru muncul di Devices table + DeviceDetail
QA Scenarios:
Scenario: Change IP successfully Tool: Bash (curl) Preconditions: Existing device with internal_ip "10.172.21.5" Steps: 1. curl -X PUT /devices/$ID -d '{"internal_ip":"10.172.21.100"}' -H "Authorization: Bearer $ADMIN_TOKEN" 2. curl -X GET /devices/$ID -H "Authorization: Bearer $ADMIN_TOKEN" Expected Result: internal_ip = "10.172.21.100" Evidence: .sisyphus/evidence/postdeploy-t3-ip-change.txt Scenario: Duplicate IP rejected Tool: Bash (curl) Preconditions: Another device already has 10.172.21.5 Steps: 1. curl -X PUT /devices/$ID -d '{"internal_ip":"10.172.21.5"}' -H "Authorization: Bearer $ADMIN_TOKEN" Expected Result: HTTP 409, error "internal_ip already in use" Evidence: .sisyphus/evidence/postdeploy-t3-ip-duplicate.txt Scenario: Invalid IP rejected Tool: Bash (curl) Steps: 1. curl -X PUT /devices/$ID -d '{"internal_ip":"not-an-ip"}' -H "Authorization: Bearer $ADMIN_TOKEN" Expected Result: HTTP 400, error "invalid internal_ip format" Evidence: .sisyphus/evidence/postdeploy-t3-ip-invalid.txtCommit: YES
- Message:
feat(api): add internal_ip update to device endpoint
- Tambah
(Tambahan) T4: Fix port range firewall agar bisa kosong
What to do:
- models.go (
apps/server-core/internal/models/models.go:100): UbahDestPortRange string \gorm:"not null;size:32"`` menjadi:DestPortRange string `gorm:"size:32;default:''"` - rules.go (
apps/server-core/api/rules.go:44): UbahDestPortRange string \json:"dest_port_range" binding:"required"`` menjadi:DestPortRange string `json:"dest_port_range"` - rules.go (handler
Createdirules.go): Tambah default empty string jika kosong:if req.DestPortRange == "" { req.DestPortRange = "" // "all ports" } - rules.go (nftables sync
rules.go:126-134): JikaDestPortRangekosong, jangan generate dport match di nftables:if rule.DestPortRange != "" { // parse port range dan apply } else { // rule tanpa port filter — berlaku untuk semua port } - FirewallEditor.vue (
apps/dashboard-ui/src/components/FirewallEditor.vue:12): Hapusrequired, ganti placeholder:<input v-model="form.port" placeholder="80 or 8000-9000 (kosongkan untuk semua port)">
Must NOT do:
- Jangan hapus migrate/rollback — GORM AutoMigrate handle kolom not null → nullable
- Jangan ubah nftables InitNetwork atau default policy
Commit: YES — fix(firewall): allow empty port range for all-port rules
Wave 2: Device-Agent Registration & Config Delivery (Plan Terpisah)
Klarifikasi User (Sudah Dijawab)
| Pertanyaan | Jawaban |
|---|---|
| Auth method | API key per-device. User register → device-agent daftar dengan API key → User approve |
| Encryption | Keduanya: endpoint encrypted (production) + endpoint plaintext (debug) |
| Scope | Yang aman, tapi bisa di-debug dengan official WireGuard client |
Short Term (NOW) — Pakai existing flow untuk official WG client:
Official WireGuard client sudah bisa dapat config via:
- Admin export:
GET /devices/:id/config(admin only) → return plaintext WireGuard config - QR Code:
GET /devices/:id/config/qr(admin only) → scan di mobile WG app - Download .conf: Via UI DeviceDetail → button Show Config
→ Ini sudah jalan. Tidak perlu perubahan. Admin tinggal export config untuk device official WG client.
Medium Term — Device-Agent Auto-Registration
Flow yang diminta user:
1. Admin create device di dashboard → system generate API key
2. Device-agent daftar dengan API key → server validasi
3. Admin approve device (atau auto-approve)
4. Device-agent dapat akses ke endpoint config
5. GET /device/config → return encrypted_config (AES-256-GCM)
6. GET /admin/device/:id/config/debug → return plaintext (admin only, untuk debug)
✅ Config endpoint plaintext untuk debugging SUDAH ADA (GET /devices/:id/config admin-only)
Yang perlu dibangun (device-agent plan terpisah):
- Backend: API key generation endpoint (generate key per device)
- Backend: Device self-registration endpoint (POST /devices/register dengan API key)
- Backend: Approval flow (auto atau manual)
- Backend: New endpoint
GET /device/config(auth with API key) → return encrypted_config - Device-agent: Registration flow (daftar dengan API key, simpan credential)
- Device-agent: Config fetch & decryption flow
+ 🔜 Akan dijadikan plan terpisah: `device-agent-registration.md`
Untuk sekarang, fokus ke T3 (IP Change) dulu karena itu yang blocking.
Final Verification Wave
- F1. Build Verification — go build + npm build PASS
- F2. DNS clear test — code diterapkan, build PASS
- F3. IP change test — code di-commit oleh subagent, build PASS
- F4. Config endpoint test — Wave 2 (device-agent registration plan)
Commit Strategy
- T1:
fix(ui): allow clearing DNS override to empty✅ - T3:
feat(api): add internal_ip update to device endpoint✅ - T4:
fix(firewall): allow empty port range for all-port rules✅ - Wave 2: Device-Agent Registration (plan terpisah nanti)
Success Criteria
Verification Commands
go build -tags dev ./... # Expected: PASS
cd apps/dashboard-ui && npm run build # Expected: PASS
Final Checklist
- DNS override bisa dikosongkan
- Debug procedure documented (informational)
- IP bisa diganti via API + UI
- Config delivery endpoint Wave 2
- Semua build pass