Files
Nexus-Guard-Suite/.sisyphus/plans/wg-auto-up-privatekey-fix.md
T
datadunia 075f915a66
NexusGuard CI / server-core-test (push) Failing after 47s
NexusGuard CI / device-agent-test (push) Failing after 35s
NexusGuard CI / dashboard-ui-build (push) Failing after 31s
chore: archive old plans, add new plan docs, update submodules
2026-05-28 02:50:43 +07:00

7.5 KiB

Plan: WG Auto Up + PrivateKey Node Registration

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

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":"<valid_base64_privkey>","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:
      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
    

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

Commit Strategy

  • Commit #1: Backend — auto wg up on startup + accept PrivateKey in server create
  • Commit #2: Frontend — PrivateKey input option in Register Node form