Files
Nexus-Guard-Suite/.sisyphus/plans/bugfix-post-deploy.md
T
datadunia 0d2388b136
NexusGuard CI / server-core-test (push) Failing after 31s
NexusGuard CI / device-agent-test (push) Failing after 28s
NexusGuard CI / dashboard-ui-build (push) Failing after 36s
fix(ops): add down before build+up in update.sh + submodule refs
2026-05-26 01:09:24 +07:00

13 KiB

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

  • 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: downbuildup -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:7docker 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
    
  • 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"
  • 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):
      <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):
      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
  • 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
  • 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 Auditoracle Verify: update.sh restructured, label fixed, backfill done, config error visible, admin guard in place

  • F2. Code Quality Reviewunspecified-high Run go build ./..., npm run build, check for unused imports, console.log

  • F3. Real Manual QAunspecified-high Execute all QA scenarios from all 5 tasks

  • F4. Scope Fidelity Checkdeep 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