cbacfea7f2
NexusGuard CI / server-core-test (push) Failing after 3m6s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 4s
NexusGuard CI / device-agent-cross-build (amd64, linux) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (amd64, windows) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (arm64, linux) (push) Has been skipped
NexusGuard CI / dashboard-test (push) Failing after 4s
NexusGuard CI / dashboard-dist (push) Has been skipped
337 lines
13 KiB
Markdown
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
|