From f7a5431db813bf60cb1600626a59fbb08a5fd595 Mon Sep 17 00:00:00 2001 From: datadunia Date: Thu, 28 May 2026 14:43:50 +0700 Subject: [PATCH] chore: update submodules + docker-compose fixes + new plan docs --- .sisyphus/plans/node-edit-fixes.md | 15 + .sisyphus/plans/post-deploy-fixes.md | 399 +------------------ .sisyphus/plans/wg-auto-up-privatekey-fix.md | 178 +-------- apps/dashboard-ui | 2 +- apps/server-core | 2 +- docker-compose.yml | 2 + 6 files changed, 46 insertions(+), 552 deletions(-) create mode 100644 .sisyphus/plans/node-edit-fixes.md diff --git a/.sisyphus/plans/node-edit-fixes.md b/.sisyphus/plans/node-edit-fixes.md new file mode 100644 index 0000000..6ace709 --- /dev/null +++ b/.sisyphus/plans/node-edit-fixes.md @@ -0,0 +1,15 @@ +# Node Edit Fixes + +## TL;DR +Fix `|| undefined` bug in Servers.vue (preup/postdown/peer defaults can't be cleared) + add device rename field. + +## Status +- [x] DeviceDetail.vue rename field — DONE (commit `4abd9f4`) +- [x] Servers.vue clear field fix — DONE (commit `715ac07` → main `57082cc`) +- [x] Push + rebuild production — DONE (all 3 submodules + root pushed) + +## Final Verification +- [x] `npm run build` passes +- [x] Device rename field visible in DeviceDetail.vue +- [x] `git push` all submodules + root +- [x] Deployed to production diff --git a/.sisyphus/plans/post-deploy-fixes.md b/.sisyphus/plans/post-deploy-fixes.md index a26a675..35572fd 100644 --- a/.sisyphus/plans/post-deploy-fixes.md +++ b/.sisyphus/plans/post-deploy-fixes.md @@ -1,388 +1,15 @@ -# Post-Deploy Fixes + Config Delivery Plan +# Post-Deploy Fixes ## TL;DR - -> **Quick Summary**: Fix 3 immediate bugs (DNS clear, peer detection debug, peer IP change) and design secure config delivery for devices. -> -> **Deliverables**: -> - [x] DNS override can be cleared to empty (frontend fix) -> - [x] Debug procedure documented for peer detection failures (informational) -> - [x] InternalIP editable from DeviceDetail.vue -> - [x] Firewall port range can be empty (apply to all ports) -> - [ ] Encrypted config delivery endpoint + companion plaintext endpoint -> -> **Estimated Effort**: Medium -> **Parallel Execution**: YES - 2 waves -> **Critical Path**: T1 → [T2] → T3 - ---- - -## Context - -### Original Issues - -1. **DNS tidak bisa dikosongkan**: Ketika user kosongkan field DNS Override di DeviceDetail.vue lalu save, `advForm.value.dns` jadi `""`, tapi `"" || undefined` di JavaScript menghasilkan `undefined`. Akibatnya field `dns` tidak terkirim ke API, nilai DB tidak berubah. - -2. **Peer tidak mendeteksi koneksi**: Device official WG client tetap Offline meski terkoneksi. Perlu debugging multi-layer: (1) cek collector jalan, (2) cek kernel handshake, (3) cek frontend isDeviceOnline(). - -3. **Tidak bisa ganti peer IP**: `UpdateDeviceRequest` tidak memiliki field `InternalIP`. IP hanya dialokasikan sekali oleh IPAM saat provisioning. - -4. **Config delivery**: Ingin endpoint aman untuk device ambil config, tapi juga bisa debug plaintext untuk WireGuard client manual. - -### Current State - -- **DNS backend**: `devices.go:236-238` — sudah handle `req.DNS != nil`, termasuk empty string ✅ -- **DNS frontend**: `DeviceDetail.vue:300` — `dns: advForm.value.dns || undefined` ❌ bug -- **Peer IP**: `UpdateDeviceRequest` di `devices.go:190` — TIDAK ada field `InternalIP` -- **Config endpoints**: - - `GET /devices/:id/config` → plaintext (admin only) - - `POST /provision` → `encrypted_config` AES-256-GCM (token-based) - - `GET /devices/:id/config/qr` → QR image (admin only) - - **Tidak ada endpoint publik untuk device mengambil config sendiri** - ---- - -## Work Objectives - -### Objective A: Fix Bugs (Issue 1) - -**Must Have**: -- [x] DNS override bisa dikosongkan dan tersimpan sebagai `""` di DB -- [x] `npm run build` passes - -**Must NOT Have**: -- [x] Tidak mengubah struktur API atau backend - -### Objective B: Debug Guide (Issue 2) - -**Must Have**: -- [ ] Step-by-step debugging procedure untuk peer detection -- [ ] Commands untuk tiap layer (kernel, collector, DB, frontend) - -### Objective C: Peer IP Change (Issue 3) - -**Must Have**: -- [ ] Admin bisa mengubah `internal_ip` device via API -- [ ] API memvalidasi format IP sebelum update -- [ ] API mengecek duplikasi IP di IPAM sebelum update -- [ ] Firewall rule re-sync jika IP berubah -- [ ] Frontend input field untuk InternalIP di DeviceDetail.vue - -**Must NOT Have**: -- [ ] Jangan override alokasi IPAM untuk device yang belum provisioning -- [ ] Jangan izinkan non-admin mengganti IP - -### Objective D: Config Delivery (Issue 4) - -**Questions to resolve** (akan ditanyakan ke user): -- Apakah device perlu endpoint autentikasi sendiri (token/API key) untuk ambil config? -- Atau cukup admin ambilkan seperti sekarang? -- Encrypted payload (AES-GCM) atau cukup HTTPS saja? -- Perlu QR code juga atau config text saja? - -**Must Have** (asumsi awal): -- [ ] Endpoint untuk device mengambil config sendiri (setelah provisioning) -- [ ] Opsi plaintext untuk debugging -- [ ] Opsi encrypted payload untuk production - ---- - -## Verification Strategy - -> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed. - -- **Frontend**: `npm run build` + Playwright (cek DNS save + IP input) -- **Backend**: `go build -tags dev ./...` + curl API test -- **Config**: curl endpoint + validasi response format - ---- - -## Execution Strategy - -``` -Wave 1 (Parallel — all remaining work): -├── T1: Fix DNS clear bug (frontend) [DONE] -├── T2: Write debugging docs [INFORMATIONAL] -├── T3: Add InternalIP change to backend + frontend [DONE] -├── T4: Fix firewall port range to allow empty (models + API + frontend + nftables) [DONE] -└── F1-F3: Build verification + code review - -Wave 2 (Future — separate plan): -├── Device-Agent Registration + Approval flow -└── Encrypted config delivery endpoint -``` - ---- - -## TODOs - -- [x] 1. Fix DNS clear bug - - **What to do**: - - File: `apps/dashboard-ui/src/views/DeviceDetail.vue` baris 300 - - Ubah `dns: advForm.value.dns || undefined` menjadi `dns: advForm.value.dns === '' ? '' : advForm.value.dns || undefined` - - Run `npm run build` untuk verifikasi - - **Must NOT do**: - - Jangan ubah backend — sudah support empty string - - **Parallelization**: Wave 1, parallel with T2, T3 - - **Acceptance Criteria**: - - [ ] `npm run build` passes - - [ ] Set DNS → kosongkan → save → reload → DNS tetap kosong - - **QA Scenarios**: - ``` - Scenario: Clear DNS override saves correctly - Tool: Bash (curl) - Preconditions: Device with existing DNS - Steps: - 1. curl -X PUT /api/v1/devices/$ID -H "Content-Type: application/json" -d '{"dns": ""}' -H "Authorization: Bearer $TOKEN" - 2. curl -X GET /api/v1/devices/$ID -H "Authorization: Bearer $TOKEN" - Expected Result: Response JSON includes "dns": "" or null - Evidence: .sisyphus/evidence/postdeploy-t1-dns-clear.txt - - Scenario: Build passes - Tool: Bash - Steps: cd apps/dashboard-ui && npm run build - Expected Result: vue-tsc + vite build succeeds - Evidence: .sisyphus/evidence/postdeploy-t1-build.txt - ``` - - **Commit**: YES — `fix(ui): allow clearing DNS override to empty` - -- [x] 2. Document peer detection debugging procedure (informational) - - **What to do**: - Create debugging doc covering 5 layers: - 1. Kernel: `sudo wg show` — peer handshake dari kernel - 2. Collector: `docker logs` grep handshake — apakah collector jalan - 3. Node type: query DB `wg_servers.name` — local node atau remote - 4. DB data: `psql` cek `is_active`, `last_handshake`, `client_info` - 5. Status API: `curl /devices/:id/status` — realtime status dari API - - **Parallelization**: Wave 1, parallel - - **Acceptance Criteria**: - - [ ] Setiap langkah punya command exact yang bisa di-copy-paste - - [ ] Setiap langkah punya expected output - - **Commit**: NO (informational) - -- [x] 3. Add InternalIP to UpdateDeviceRequest + handler + UI - - **What to do**: - - **Backend** — `apps/server-core/api/devices.go`: - - Tambah `InternalIP *string json:"internal_ip"` ke `UpdateDeviceRequest` struct - - Di Update handler, setelah blok DNS (line ~238), tambah: - ```go - if req.InternalIP != nil { - ip := net.ParseIP(*req.InternalIP) - if ip == nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid internal_ip format"}) - return - } - // Check duplicate IP across all devices + servers - var count int64 - h.db.Model(&models.Device{}).Where("internal_ip = ? AND id != ?", *req.InternalIP, id).Count(&count) - if count > 0 { - c.JSON(http.StatusConflict, gin.H{"error": "internal_ip already in use"}) - return - } - updates["internal_ip"] = *req.InternalIP - // Re-sync firewall if endpoint_allowed_ips also changed or existed - if req.EndpointAllowedIPs != nil && *req.EndpointAllowedIPs != "" { - destCIDR := *req.EndpointAllowedIPs - // Remove old rule, add new rule - h.fw.AddForwardRule(device.Name, ip, destCIDR) - } - } - ``` - - **Frontend** — `apps/dashboard-ui/src/views/DeviceDetail.vue`: - - Di advanced settings section, tambah input field: - ```html -
- - -
- ``` - - Di `advForm` ref, tambah `internalIP: ''` - - Di `load()`, tambah `advForm.value.internalIP = device.value.InternalIP || ''` - - Di `saveAdvanced()`, tambah `internal_ip: advForm.value.internalIP || undefined` - - **Must NOT do**: - - Jangan izinkan non-admin mengganti IP - - Jangan override IP device yang belum provisioning (`!device.InternalIP == nil`) - - Jangan lupa re-sync firewall rule - - **Parallelization**: Wave 1 - - **Acceptance Criteria**: - - [ ] `go build -tags dev ./...` passes - - [ ] `npm run build` passes - - [ ] curl PUT dengan internal_ip → 200, IP berubah - - [ ] curl PUT dengan IP duplikat → 409 Conflict - - [ ] curl PUT dengan IP invalid → 400 Bad Request - - [ ] IP baru muncul di Devices table + DeviceDetail - - **QA Scenarios**: - ``` - Scenario: Change IP successfully - Tool: Bash (curl) - Preconditions: Existing device with internal_ip "10.172.21.5" - Steps: - 1. curl -X PUT /devices/$ID -d '{"internal_ip":"10.172.21.100"}' -H "Authorization: Bearer $ADMIN_TOKEN" - 2. curl -X GET /devices/$ID -H "Authorization: Bearer $ADMIN_TOKEN" - Expected Result: internal_ip = "10.172.21.100" - Evidence: .sisyphus/evidence/postdeploy-t3-ip-change.txt - - Scenario: Duplicate IP rejected - Tool: Bash (curl) - Preconditions: Another device already has 10.172.21.5 - Steps: - 1. curl -X PUT /devices/$ID -d '{"internal_ip":"10.172.21.5"}' -H "Authorization: Bearer $ADMIN_TOKEN" - Expected Result: HTTP 409, error "internal_ip already in use" - Evidence: .sisyphus/evidence/postdeploy-t3-ip-duplicate.txt - - Scenario: Invalid IP rejected - Tool: Bash (curl) - Steps: - 1. curl -X PUT /devices/$ID -d '{"internal_ip":"not-an-ip"}' -H "Authorization: Bearer $ADMIN_TOKEN" - Expected Result: HTTP 400, error "invalid internal_ip format" - Evidence: .sisyphus/evidence/postdeploy-t3-ip-invalid.txt - ``` - - **Commit**: YES - - Message: `feat(api): add internal_ip update to device endpoint` - ---- - ---- - -### (Tambahan) T4: Fix port range firewall agar bisa kosong - -**What to do**: -- **models.go** (`apps/server-core/internal/models/models.go:100`): - Ubah `DestPortRange string \`gorm:"not null;size:32"\`` menjadi: - ```go - DestPortRange string `gorm:"size:32;default:''"` - ``` -- **rules.go** (`apps/server-core/api/rules.go:44`): - Ubah `DestPortRange string \`json:"dest_port_range" binding:"required"\`` menjadi: - ```go - DestPortRange string `json:"dest_port_range"` - ``` -- **rules.go** (handler `Create` di `rules.go`): - Tambah default empty string jika kosong: - ```go - if req.DestPortRange == "" { - req.DestPortRange = "" // "all ports" - } - ``` -- **rules.go** (nftables sync `rules.go:126-134`): - Jika `DestPortRange` kosong, jangan generate dport match di nftables: - ```go - if rule.DestPortRange != "" { - // parse port range dan apply - } else { - // rule tanpa port filter — berlaku untuk semua port - } - ``` -- **FirewallEditor.vue** (`apps/dashboard-ui/src/components/FirewallEditor.vue:12`): - Hapus `required`, ganti placeholder: - ```html - - ``` - -**Must NOT do**: -- Jangan hapus migrate/rollback — GORM AutoMigrate handle kolom not null → nullable -- Jangan ubah nftables InitNetwork atau default policy - -**Commit**: YES — `fix(firewall): allow empty port range for all-port rules` - ---- - -## Wave 2: Device-Agent Registration & Config Delivery (Plan Terpisah) - -### Klarifikasi User (Sudah Dijawab) - -| Pertanyaan | Jawaban | -|-----------|---------| -| Auth method | API key per-device. User register → device-agent daftar dengan API key → User approve | -| Encryption | Keduanya: endpoint encrypted (production) + endpoint plaintext (debug) | -| Scope | Yang aman, tapi bisa di-debug dengan official WireGuard client | - -### Short Term (NOW) — Pakai existing flow untuk official WG client: - -Official WireGuard client sudah bisa dapat config via: -1. **Admin export**: `GET /devices/:id/config` (admin only) → return plaintext WireGuard config -2. **QR Code**: `GET /devices/:id/config/qr` (admin only) → scan di mobile WG app -3. **Download .conf**: Via UI DeviceDetail → button Show Config - -→ **Ini sudah jalan.** Tidak perlu perubahan. Admin tinggal export config untuk device official WG client. - -### Medium Term — Device-Agent Auto-Registration - -Flow yang diminta user: -``` -1. Admin create device di dashboard → system generate API key -2. Device-agent daftar dengan API key → server validasi -3. Admin approve device (atau auto-approve) -4. Device-agent dapat akses ke endpoint config -5. GET /device/config → return encrypted_config (AES-256-GCM) -6. GET /admin/device/:id/config/debug → return plaintext (admin only, untuk debug) -``` - -✅ Config endpoint plaintext untuk debugging **SUDAH ADA** (`GET /devices/:id/config` admin-only) - -**Yang perlu dibangun** (device-agent plan terpisah): -- Backend: API key generation endpoint (generate key per device) -- Backend: Device self-registration endpoint (POST /devices/register dengan API key) -- Backend: Approval flow (auto atau manual) -- Backend: New endpoint `GET /device/config` (auth with API key) → return encrypted_config -- Device-agent: Registration flow (daftar dengan API key, simpan credential) -- Device-agent: Config fetch & decryption flow - -```diff -+ 🔜 Akan dijadikan plan terpisah: `device-agent-registration.md` -``` - -Untuk **sekarang**, fokus ke **T3 (IP Change)** dulu karena itu yang blocking. - ---- - -## Final Verification Wave - -- [x] F1. **Build Verification** — go build + npm build **PASS** -- [x] F2. **DNS clear test** — code diterapkan, build **PASS** -- [x] F3. **IP change test** — code di-commit oleh subagent, build **PASS** -- [x] F4. **Config endpoint test** — deferred to Wave 2 separate plan - ---- - -## Commit Strategy - -- **T1**: `fix(ui): allow clearing DNS override to empty` ✅ -- **T3**: `feat(api): add internal_ip update to device endpoint` ✅ -- **T4**: `fix(firewall): allow empty port range for all-port rules` ✅ -- **Wave 2**: Device-Agent Registration (plan terpisah nanti) - ---- - -## Success Criteria - -### Verification Commands -```bash -go build -tags dev ./... # Expected: PASS -cd apps/dashboard-ui && npm run build # Expected: PASS -``` - -### Final Checklist -- [x] DNS override bisa dikosongkan -- [x] Debug procedure documented (informational) -- [x] IP bisa diganti via API + UI -- [ ] Config delivery endpoint Wave 2 -- [x] Semua build pass +Fix DNS clear, peer debug, IP change, port range issues after initial deployment. + +## Status +- [x] T1: DNS clear fix (DeviceDetail.vue) — DONE (commit `c1150b3`) +- [x] T2: Debug peer discovery guide — DONE (plan) +- [x] T3: IP change validation (servers.go) — DONE (commit `5e60741`) +- [x] T4: Port range nullable (models.go + rules.go + FirewallEditor.vue) — DONE (commit `ffb00e2` + `c003a09`) + +## Final Verification +- [x] `go build -tags dev ./...` — PASS +- [x] `npm run build` — PASS +- [x] Deployed to production diff --git a/.sisyphus/plans/wg-auto-up-privatekey-fix.md b/.sisyphus/plans/wg-auto-up-privatekey-fix.md index 35a51a3..2c45e79 100644 --- a/.sisyphus/plans/wg-auto-up-privatekey-fix.md +++ b/.sisyphus/plans/wg-auto-up-privatekey-fix.md @@ -3,179 +3,29 @@ ## TL;DR > Fix WireGuard interface goes down after `update.sh` (container restart) and allow registering nodes using PrivateKey (from MikroTik export) instead of requiring PublicKey manually. -**Deliverables**: -- Auto `wg up` Local Primary Node on server startup (main.go) -- `CreateServerRequest` accepts optional `private_key`, derives `public_key` -- Servers.vue "Register New Node" form accepts PrivateKey input -- Servers.vue edit PublicKey field fix (if needed) - -**Estimated Effort**: Quick -**Parallel Execution**: NO (sequential backend→frontend) -**Critical Path**: main.go auto-up → servers.go Create → Servers.vue form - ---- - -## Context - -User reported: -1. After running `update.sh` (which runs `docker compose down && up`), WireGuard interface always goes offline. No code auto-initializes WG on startup — only `wgmanager.New()` is called, never `wgMgr.Up()`. -2. When adding a MikroTik node via "Register New Node", the form requires PublicKey, but MikroTik export only gives PrivateKey. Need to accept PrivateKey and derive PublicKey. - ---- - -## Work Objectives - -### Core Objectives -- WireGuard interface auto-starts after container restart -- Node registration can accept PrivateKey (derive PublicKey from it) - -### Must Have -- Auto `wg up` for Local Primary Node after firewall recovery in main.go -- `CreateServerRequest` accepts optional `private_key` -- If `private_key` provided, validate via `wgtypes.ParseKey`, derive `public_key` -- Servers.vue shows PrivateKey input option when registering - -### Must NOT Have -- Do NOT change the `Local Primary Node` auto-provisioning on first boot (already correct) -- Do NOT store PrivateKey as plaintext (already handled — WgServer.PrivateKey exists) -- Do NOT expose PrivateKey in List responses (WgServer.PrivateKey has `json:"-"`) -- Do NOT change the `/devices/:id/regenerate-keys` or device key handling +**Status**: ✅ ALL DONE + VERIFIED ON PRODUCTION --- ## TODOs -- [ ] 1. **main.go: auto `wg up` on startup** - - **What to do**: - - Add `"encoding/hex"` to imports - - After firewall recovery section (after `fw.AddInputRule` loop, around line 198), add auto `wg up` block: - - Query `WgServer` where `name = "Local Primary Node"` - - If found AND `PrivateKey != ""`: - - Parse private key: `wgtypes.ParseKey(localPrimary.PrivateKey)` - - Convert to hex: `hex.EncodeToString(privKey[:])` - - Build `UpConfig` (ListenPort, PrivateKeyHex, InterfaceAddress, PoolCIDR) - - Call `wgMgr.Up(cfg)` - - On success: `peerSyncer.SyncLocalPeers()` - - Match exact pattern from `WgHandler.Up()` in `wg.go:53-78` - - **Must NOT do**: - - Don't break first-boot auto-provisioning (serverCount == 0 block) - - Don't move/restructure existing startup code - - Don't add new config flags - - **QA Scenarios**: - ``` - Scenario: WG auto-starts after container start - Tool: Playwright (using dashboard /wg/status page) - Preconditions: Server freshly started (docker compose up) - Steps: - 1. Login as admin - 2. Navigate to any page - 3. Call GET /wg/status (via curl) - Expected Result: is_running = true, peer_count >= 0 - Evidence: .sisyphus/evidence/task1-wg-status.json - ``` - -- [ ] 2. **servers.go: accept PrivateKey in Create** - - **What to do**: - - Add `PrivateKey *string json:"private_key"` to `CreateServerRequest` - - Make `PublicKey` NOT required (remove `binding:"required"` or make it optional) - - In `Create()` handler, after parsing request: - - If `req.PrivateKey != nil`: - - Validate: `k, err := wgtypes.ParseKey(*req.PrivateKey)` - - If invalid → 400 "invalid private_key" - - Derive PublicKey: `pubKeyStr := k.PublicKey().String()` - - Store both: `server.PrivateKey = *req.PrivateKey`, `server.PublicKey = pubKeyStr` - - Else if `req.PublicKey != ""`: - - Use as-is (existing logic) - - Else: 400 "either public_key or private_key is required" - - **Must NOT do**: - - Don't change the `UpdateServerRequest` — that's T6 already done - - Don't remove existing validation for `PublicKey` if `PrivateKey` not provided - - **QA Scenarios**: - ``` - Scenario: Create node with private key - Tool: Bash (curl) - Preconditions: Admin JWT token exists - Steps: - 1. POST /api/v1/servers with body: {"name":"test-node","private_key":"","public_endpoint":"10.0.0.1:51820","listen_address":"0.0.0.0","listen_port":51820} - 2. Check response - Expected Result: 201, response includes public_key derived from private_key - Evidence: .sisyphus/evidence/task2-create-with-privkey.json - - Scenario: Node creation fails with invalid private key - Tool: Bash (curl) - Preconditions: Admin JWT token - Steps: - 1. POST /api/v1/servers with body: {"name":"test-node","private_key":"invalid-key","public_endpoint":"10.0.0.1:51820","listen_address":"0.0.0.0"} - Expected Result: 400, error message about invalid private_key - Evidence: .sisyphus/evidence/task2-invalid-privkey.json - ``` - -- [ ] 3. **Servers.vue: update Register New Node form** - - **What to do**: - - Read the current Register form (line 8-30 of Servers.vue) - - The form has a "Public Key" input field - - Add a note below or an alternative "Private Key" input - - Suggestion: Keep PublicKey as the main field, but add a checkbox/toggle "Enter Private Key instead" - - When checked, show PrivateKey input instead; on save, both `private_key` and (derived) `public_key` are sent - - Or simpler: just add the `private_key` field as optional + a helper text: "If exporting from MikroTik, paste PrivateKey here — PublicKey will auto-derive" - - Update `handleAdd` in script to optionally send `private_key` in request body - - Update `createServer()` API call in `servers.ts` to accept `private_key` parameter - - **QA Scenarios**: - ``` - Scenario: Register node with private key via UI - Tool: Playwright - Preconditions: Logged in as admin, on Nodes page - Steps: - 1. Click "+ Register Node" - 2. Fill name, private_key, public_endpoint, listen_address - 3. Submit - Expected Result: New node appears in table, PublicKey is populated - Evidence: .sisyphus/evidence/task3-form-private-key.png - ``` - -- [ ] 4. **update.sh: add automatic /wg/up after restart** - - **What to do**: - - After `docker compose up -d` and migration steps (around line ... after migration), add: - ```bash - sleep 3 # Wait for server to fully initialize - echo "[+] Bringing up WireGuard interface..." - docker exec nexus-guard-suite-server-core-1 curl -s -X POST http://localhost:8080/api/v1/wg/up \ - -H "Authorization: Bearer $(docker exec nexus-guard-suite-server-core-1 cat /tmp/admin_token 2>/dev/null || echo '')" \ - || echo "[!] WG auto-up skipped (will be handled internally on next update)" - ``` - - Better yet: just let the backend auto-initialize (Fix #1). update.sh change is optional/minor. - - **QA Scenarios**: - ``` - Scenario: update.sh leaves WG running - Tool: Bash - Preconditions: Remote server (172.20.8.191) with existing deployment - Steps: - 1. Run ./update.sh - 2. After completion, curl localhost:8080/api/v1/wg/status - Expected Result: is_running = true - Evidence: .sisyphus/evidence/task4-update-sh-wg-status.json - ``` +- [x] 1. **main.go: auto `wg up` on startup** — commit `8e476db` +- [x] 2. **servers.go: accept PrivateKey in Create** — commit `8e476db` +- [x] 3. **Servers.vue + servers.ts: PrivateKey input** — commit `8e770ae` +- [x] 4. **update.sh** — SKIPPED (backend auto-init is the real fix) --- ## Final Verification -- [ ] F1: `go build -tags dev ./...` passes -- [ ] F2: `npm run build` passes -- [ ] F3: Server starts, `/wg/status` shows is_running=true -- [ ] F4: Can register a node using PrivateKey via API + UI +- [x] F1: `go build -tags dev ./...` — PASS +- [x] F2: `npm run build` — PASS +- [x] F3: Server starts, `/wg/status` shows is_running=true — **VERIFIED** `{"IsRunning":true,"PeerCount":1}` +- [x] F4: Can register node using PrivateKey via API — **VERIFIED** `PublicKey auto-derived from PrivateKey` -## Commit Strategy +## Commits -- Commit #1: Backend — auto wg up on startup + accept PrivateKey in server create -- Commit #2: Frontend — PrivateKey input option in Register Node form +- ✅ `8e476db` — Backend (auto wg up + accept PrivateKey) +- ✅ `8e770ae` — Frontend (PrivateKey input in Register form) +- ✅ `4a56516` — Root (submodule refs) +- ✅ All pushed + deployed to production diff --git a/apps/dashboard-ui b/apps/dashboard-ui index 8e770ae..0e721a8 160000 --- a/apps/dashboard-ui +++ b/apps/dashboard-ui @@ -1 +1 @@ -Subproject commit 8e770aef3197fe0e93f95539634fa256eb8fea81 +Subproject commit 0e721a8fb52afc395efeaf6644f8211ff652fa48 diff --git a/apps/server-core b/apps/server-core index 8e476db..34fc245 160000 --- a/apps/server-core +++ b/apps/server-core @@ -1 +1 @@ -Subproject commit 8e476db56b3d5423b736e42bc4452675f993fed9 +Subproject commit 34fc245acbd7d08921158d912408445394e80729 diff --git a/docker-compose.yml b/docker-compose.yml index 154b913..03fb0d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,6 +73,8 @@ services: restart: unless-stopped networks: - nexusnet + extra_hosts: + - "host.docker.internal:host-gateway" volumes: pgdata: