# 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
