diff --git a/.sisyphus/plans/bugfix-post-deploy.md b/.sisyphus/plans/bugfix-post-deploy.md new file mode 100644 index 0000000..41f653b --- /dev/null +++ b/.sisyphus/plans/bugfix-post-deploy.md @@ -0,0 +1,336 @@ +# Bug Fix: Post-Deploy Issues (update.sh, Node CIDR, Config Visibility) + +## TL;DR + +> **Quick Summary**: Perbaiki 3 post-deployment bugs — update.sh tidak `down` sebelum restart, label Interface Address membingungkan + backfill untuk node lama tanpa InterfaceAddress, dan config wg tidak muncul/tidak ada error feedback di PeerConfigModal. +> +> **Deliverables**: +> - update.sh restart dengan `down` + `build` + `up` +> - Label "Interface Address (CIDR)" + backfill InterfaceAddress untuk existing node +> - PeerConfigModal loading state + error visibility + admin guard +> +> **Estimated Effort**: Quick (4 tasks) +> **Parallel Execution**: YES — 2 waves +> **Critical Path**: Task 1-4 parallel → Task 5-6 independent + +--- + +## Context + +### Original Request +3 bugs user setelah deploy: + +1. **update.sh**: `docker compose build` + `up -d` tanpa `down` — container tidak proper restart. Perubahan .env tidak teraplikasi. +2. **Node WG tidak punya IP default**: Label "Interface IP / Prefix" membingungkan (seharusnya CIDR). Node lama tanpa InterfaceAddress → IPAM bisa alokasikan IP server ke peer (**IP collision risk**). +3. **Config wg tidak muncul**: PeerConfigModal tidak menunjukkan config. Error loading silent — `loadConfig` catch block tidak set `configError`. + +### Metis Review — Critical Findings +- **IPAM collision risk (CRITICAL)**: `ipam/manager.go:92-94` query `WHERE interface_address IS NOT NULL AND interface_address != ''`. Jika InterfaceAddress kosong, server IP tidak dikecualikan → peer bisa dapat IP server. Backfill InterfaceAddress bukan hanya cosmetic. +- **Admin intentional**: `GET /devices/:id/config` sengaja admin-only. Frontend harus hidden untuk non-admin. +- **Silent error chain**: 403 dari config endpoint → catch block di `loadConfig` set `editableConfig = ''` tapi tidak set `configError` → user lihat textarea kosong tanpa error. + +--- + +## Work Objectives + +### Core Objective +Fix 3 post-deployment bugs yang menghalangi user menggunakan NexusGuard setelah update. + +### Concrete Deliverables +1. **update.sh**: Restart logic — `down` sebelum `build && up -d` +2. **Servers.vue**: Label "Interface Address (CIDR)" +3. **Migration**: Backfill InterfaceAddress untuk existing node +4. **PeerConfigModal.vue**: Loading state + error visibility +5. **Devices.vue + DeviceDetail.vue**: Admin guard di Config button + +### Must Have +- [ ] update.sh: `docker compose down` sebelum build +- [ ] Label form: dari "Interface IP / Prefix" → "Interface Address (CIDR)" +- [ ] Semua existing node dengan `interface_address = ''` ter-backfill setelah migration +- [ ] PeerConfigModal tampilkan "Loading..." saat fetching config +- [ ] PeerConfigModal tampilkan error merah jika load gagal +- [ ] Non-admin tidak melihat Config button + +### Must NOT Have (Guardrails) +- **JANGAN** `docker compose down -v` (jangan hapus volume) +- **JANGAN** buka endpoint config/QR/share ke non-admin +- **JANGAN** refactor IPAM, migration framework, atau buat endpoint baru +- **JANGAN** ubah behavior WireGuard (config-in-memory tetap) +- **JANGAN** log plaintext keys + +--- + +## Verification Strategy + +> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. + +### Test Decision +- **Infrastructure exists**: YES +- **Automated tests**: NO (bug fix, verification via QA scenarios) +- **Framework**: vitest (existing) + +### QA Policy +- **update.sh**: Simulasi dengan bash command +- **Frontend**: Playwright untuk cek label + button visibility +- **Backend**: Bash (curl) untuk endpoint verification +- **DB**: Bash (docker exec psql) untuk backfill verification + +--- + +## Execution Strategy + +### Parallel Execution Waves + +``` +Wave 1 (All parallel — independent fixes): +├── Task 1: Fix update.sh — down before build+up [quick] +├── Task 2: Fix form label — "Interface Address (CIDR)" [quick] +├── Task 3: Fix PeerConfigModal — loading + error visibility [quick] +└── Task 4: Add admin guard to Config button [quick] + +Wave 2 (Backend backfill — depends on nothing): +├── Task 5: Create InterfaceAddress backfill migration [quick] + +Wave FINAL: +├── F1: Plan Compliance + F2: Code Quality + F3: QA + F4: Scope +``` + +### Dependency Matrix +- **1-4**: None — all can run in parallel +- **5**: None — independent +- **F1-F4**: All tasks complete + +--- + +## TODOs + +- [x] 1. Fix update.sh — add `docker compose down` before build+up + + **What to do**: + - `D:\www-project\NexusGuard\update.sh:28-31` + - Tambah `docker compose down` SEBELUM `docker compose build` + - Urutan baru: `down` → `build` → `up -d` + - Jangan tambah `-v` flag (volume data harus aman) + + **Must NOT do**: + - JANGAN tambah `-v` atau `--volumes` + - JANGAN hapus migration step + - JANGAN ubah Makefile atau docker-compose.yml + + **Recommended Agent Profile**: + - Category: `quick` + - Skills: [] + + **References**: + - `D:\www-project\NexusGuard\update.sh:28-31` — Current build+up lines + - `D:\www-project\NexusGuard\Makefile:7` — `docker compose down` pattern (safe, no -v) + + **Acceptance Criteria**: + - [ ] `docker compose down` runs BEFORE `docker compose build` + - [ ] Volume pgdata tetap ada setelah update + + **QA Scenarios**: + ``` + Scenario: Verify down runs before up + Tool: Bash + Preconditions: update.sh exists + Steps: + 1. Read update.sh lines 28-32 + 2. Verify `docker compose down` appears before `docker compose build` + 3. Verify NO `-v` flag on down + Expected Result: Correct order with safe flags + Evidence: .sisyphus/evidence/task-1-update-sh.txt + ``` + +- [x] 2. Fix form label — "Interface Address (CIDR)" + + **What to do**: + - `apps/dashboard-ui/src/views/Servers.vue`: + - Cari label "Interface IP / Prefix" di create form dan edit form + - Ganti ke "Interface Address (CIDR)" + - Jangan ubah placeholder (`10.172.20.1/24`) — sudah benar + + **Must NOT do**: + - JANGAN ubah logika parseIpInput atau validasi + - JANGAN ubah struktur form + + **Parallelization**: + - Can Run In Parallel: YES + - Parallel Group: Wave 1 (with Tasks 1, 3, 4) + + **References**: + - `apps/dashboard-ui/src/views/Servers.vue:49` — Create form label + - `apps/dashboard-ui/src/views/Servers.vue:187` — Edit form label + + **Acceptance Criteria**: + - [ ] Create form label: "Interface Address (CIDR)" + - [ ] Edit form label: "Interface Address (CIDR)" + - [ ] Placeholder tetap "10.172.20.1/24" + +- [x] 3. Fix PeerConfigModal — loading state + error visibility + + **What to do**: + - `apps/dashboard-ui/src/components/PeerConfigModal.vue`: + + **A. Loading state**: + - Tambah ref: `const configLoading = ref(false)` + - Di `loadConfig()`: set `configLoading = true` sebelum API call, `configLoading = false` setelah + - Di template, di atas textarea (atau di dalamnya): + ```html +
{{ configError }}
` + + **C. Same fix for QR error**: + - `loadQR()` catch block juga — set configError atau qrError jika ada + + **Must NOT do**: + - JANGAN refactor struktur modal atau tambah fitur baru + - JANGAN ubah endpoint backend + + **Parallelization**: + - Can Run In Parallel: YES + - Parallel Group: Wave 1 (with Tasks 1, 2, 4) + + **References**: + - `apps/dashboard-ui/src/components/PeerConfigModal.vue:85-95` — loadConfig function + - `apps/dashboard-ui/src/components/PeerConfigModal.vue:27` — textarea + - `apps/dashboard-ui/src/components/PeerConfigModal.vue:36` — configError template (already exists) + - `apps/dashboard-ui/src/components/PeerConfigModal.vue:97-103` — loadQR (same pattern) + - `apps/dashboard-ui/src/components/PeerConfigModal.vue:155-163` — saveConfig catch block (correct pattern to follow) + + **Acceptance Criteria**: + - [ ] Textarea menampilkan "Loading configuration..." saat fetch + - [ ] Load gagal (403, network error) → textarea hilang, error merah muncul + - [ ] Load sukses → textarea muncul dengan config + - [ ] QR juga handle error dengan baik + +- [x] 4. Add admin guard to Config button + + **What to do**: + - `apps/dashboard-ui/src/views/Devices.vue:39`: + - Ubah `v-if="device.InternalIP"` → `v-if="device.InternalIP && authStore.isAdmin"` + - Import/akses authStore: `const authStore = useAuthStore()` (cek existing usage) + + - `apps/dashboard-ui/src/views/DeviceDetail.vue:98`: + - Ubah `v-if="device.InternalIP"` → `v-if="device.InternalIP && authStore.isAdmin"` + + **Must NOT do**: + - JANGAN buka endpoint ke non-admin + - JANGAN buat fallback untuk non-admin (tidak usah tampilkan "login as admin") + + **Parallelization**: + - Can Run In Parallel: YES + - Parallel Group: Wave 1 (with Tasks 1, 2, 3) + + **References**: + - `apps/dashboard-ui/src/views/Devices.vue:39` — Config button + - `apps/dashboard-ui/src/views/DeviceDetail.vue:98` — Config button + - `apps/dashboard-ui/src/views/Devices.vue:1` — Import pattern untuk store + + **Acceptance Criteria**: + - [ ] Admin: Config button visible + - [ ] Non-admin: Config button hidden + +- [x] 5. Backfill InterfaceAddress untuk existing node + + **What to do**: + - Buat Go CLI flag `-backfill-interface` di `apps/server-core/main.go`: + - Query semua server dengan `interface_address IS NULL OR interface_address = ''` + - Untuk setiap server, panggil `calcInterfaceAddress(server.IPPoolCIDR)` + - Update `interface_address` jika `IPPoolCIDR` tidak kosong + - Skip jika `IPPoolCIDR` juga kosong + + - Atau lebih simple: SQL migration file + - Tapi Go lebih aman karena reuse `calcInterfaceAddress` logic + + - **Pattern**: Ikuti existing CLI flag pattern (`-create-admin`, `-migrate-prod`) + - `main.go:44-60` — CLI flag handling + - Gunakan `db.Model(&models.WgServer{})` seperti di `calcInterfaceAddress` + + **Must NOT do**: + - JANGAN buat migration framework baru + - JANGAN trigger IPAM re-allocation + - JANGAN ubah IP yang sudah valid — hanya isi yang kosong + + **Parallelization**: + - Can Run In Parallel: YES (independent) + + **References**: + - `apps/server-core/main.go:44-60` — CLI flag pattern + - `apps/server-core/api/servers.go:114-129` — calcInterfaceAddress function + - `apps/server-core/internal/ipam/manager.go:92-94` — Query that exposes the bug + - `apps/server-core/api/servers.go:162-171` — Create handler auto-calc (pattern to follow) + + **Acceptance Criteria**: + - [ ] `./server-core -backfill-interface` exits 0 + - [ ] Semua row dengan `interface_address = ''` terisi + - [ ] Row dengan `IPPoolCIDR = ''` tetap kosong (skip) + - [ ] IPAM tidak bisa alokasikan server IP ke peer setelah backfill + + **QA Scenarios**: + ``` + Scenario: Backfill empty InterfaceAddress + Tool: Bash + Preconditions: DB has server with IPPoolCIDR="10.0.0.0/24" and InterfaceAddress="" + Steps: + 1. docker compose run --rm server-core ./server-core -backfill-interface + 2. docker exec db psql -c "SELECT interface_address FROM wg_servers WHERE interface_address IS NULL OR interface_address = ''" + Expected Result: Query returns 0 rows + Evidence: .sisyphus/evidence/task-5-backfill.txt + + Scenario: IPAM no longer allocates server IP + Tool: Bash (curl) + Preconditions: Server with IPPoolCIDR="10.0.0.0/24", InterfaceAddress="10.0.0.1/24" + Steps: + 1. curl -s -X POST /api/v1/peers -H "Authorization: Bearer $TOKEN" -d '{"name":"test","wg_server_id":"$ID"}' + 2. Parse response, check InternalIP + Expected Result: InternalIP != "10.0.0.1" + Evidence: .sisyphus/evidence/task-5-ipam-fix.json + ``` + +--- + +## Final Verification Wave + +- [ ] F1. **Plan Compliance Audit** — `oracle` + Verify: update.sh restructured, label fixed, backfill done, config error visible, admin guard in place + +- [ ] F2. **Code Quality Review** — `unspecified-high` + Run `go build ./...`, `npm run build`, check for unused imports, console.log + +- [ ] F3. **Real Manual QA** — `unspecified-high` + Execute all QA scenarios from all 5 tasks + +- [ ] F4. **Scope Fidelity Check** — `deep` + Must Have checklist complete, Must NOT compliance + +--- + +## Commit Strategy + +- **1**: `fix(ops): add docker compose down before build+up in update.sh` +- **2**: `fix(ui): rename Interface IP/Prefix label to Interface Address (CIDR)` +- **3**: `fix(ui): add loading state and error visibility to PeerConfigModal` +- **4**: `fix(ui): hide config button for non-admin users` +- **5**: `fix(core): add backfill-interface CLI flag for existing nodes` + +--- + +## Success Criteria + +### Final Checklist +- [ ] update.sh: `docker compose down` runs before build+up +- [ ] Label form: "Interface Address (CIDR)" +- [ ] Semua existing node dengan InterfaceAddress kosong terbackfill +- [ ] PeerConfigModal tampilkan loading + error +- [ ] Non-admin tidak lihat Config button +- [ ] IPAM tidak alokasikan server IP ke peer diff --git a/apps/dashboard-ui b/apps/dashboard-ui index 44b95e0..2ed7a74 160000 --- a/apps/dashboard-ui +++ b/apps/dashboard-ui @@ -1 +1 @@ -Subproject commit 44b95e0bbb7548c0fa7017c0765c42797d76893d +Subproject commit 2ed7a748e6f95b426621a7dad76876f930f40698 diff --git a/apps/server-core b/apps/server-core index 179b3e3..00b1dfb 160000 --- a/apps/server-core +++ b/apps/server-core @@ -1 +1 @@ -Subproject commit 179b3e36969385ce5c73bd9f4de9a54837f14f34 +Subproject commit 00b1dfbee95ff13b712881fb6c215513c29a3fcc diff --git a/update.sh b/update.sh index e7e7eef..64a6075 100755 --- a/update.sh +++ b/update.sh @@ -22,7 +22,10 @@ git submodule update --init --recursive --remote || echo "[!] Git submodule upda echo "[+] Sourcing environment variables from root .env..." set -a && . .env && set +a -# 4. Rebuild Images & Restart Containers +# 4. Gracefully stop existing containers, Rebuild Images & Restart +echo "[+] Stopping existing containers..." +docker compose down + echo "[+] Rebuilding Docker images (injecting new environment variables)..." # Menggunakan --build-arg untuk memastikan Vue/Vite membaca domain API baru docker compose build --build-arg VITE_API_BASE_URL=$VITE_API_BASE_URL