Files
Nexus-Guard-Suite/.sisyphus/plans/bugfix-post-deploy.md
T
datadunia c8f1683281
NexusGuard CI / server-core-test (push) Failing after 27s
NexusGuard CI / device-agent-test (push) Failing after 31s
NexusGuard CI / dashboard-ui-build (push) Failing after 38s
chore: update plan docs, submodule ref
2026-05-26 01:27:58 +07:00

337 lines
13 KiB
Markdown

# 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
<div v-if="configLoading" class="text-gray-400 text-sm py-4">Loading configuration...</div>
<textarea v-else v-model="editableConfig" ...></textarea>
```
**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: `<p v-if="configError" class="text-xs text-red-400 mt-2">{{ configError }}</p>`
**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