# 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
Loading configuration...
``` **B. Error visibility (CRITICAL)**: - Di `loadConfig()` catch block (line 87-94): ```ts configError.value = err.response?.data?.error || err.message || 'Failed to load device configuration' ``` - Pastikan `configError` sudah di-reset ke `''` di awal loadConfig (sebelum try) - `configError` sudah ada di template line 36: `

{{ 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 - [x] F1. **Plan Compliance Audit** — `oracle` Verify: update.sh restructured, label fixed, backfill done, config error visible, admin guard in place - [x] F2. **Code Quality Review** — `unspecified-high` Run `go build ./...`, `npm run build`, check for unused imports, console.log - [x] F3. **Real Manual QA** — `unspecified-high` Execute all QA scenarios from all 5 tasks - [x] 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