# 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":"","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 ``` --- ## 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