12 KiB
NexusGuard — AI Agent Reference: Completed Work History
Purpose: Compact record of all completed implementation work. Use this to understand what's been built, key decisions, and files modified. Avoid re-implementing or regressing completed features. Generated: 2026-05-24 Format: One section per completed phase/plan. Minimal token overhead.
1. Full System Build (nxg-nexusguard-full-build)
Goal: Build complete NexusGuard SD-WAN from scratch across 3 submodules.
Stack: Go 1.25 (Gin/GORM) + Vue 3 (Vite/Pinia/Tailwind) + Device Agent (wireguard-go memory-only)
Key Decisions:
- Zero-Trust: nftables default DROP. Per-user sets for isolation.
- Stealth Agent: wireguard-go via
device.IpcSet()— no config files on disk. - Hardware Binding: SHA256(HWID + Salt) for AES key derivation.
- Config Encryption: AES-256-GCM server↔agent. Key never transmitted.
- JWT for Dashboard↔Server. X-Token-Auth for Agent↔Server (one-time reg token).
- Dev: GORM AutoMigrate. Prod: goose versioned migrations.
- No STUN/P2P, no WebSocket (polling only), no kernel WireGuard, no agent disk writes.
Guardrails:
- NEVER
nft flush table— element-level ops only - NEVER GORM AutoMigrate in production
- NEVER write WG config to
/etc/wireguard/
2. Phase 4.5: Multi-Node & Production Readiness
Goal: Single-node → Multi-Server architecture + DB startup hardening.
| Task | Files | What |
|---|---|---|
| Multi-Server DB & API | models.go, 001_init.sql, api/servers.go, main.go |
WgServerID FK on Device. CRUD /api/v1/servers. |
| Provisioning Multi-Node | api/provisioning.go, provisioning_test.go |
Remove 127.0.0.1:51820 fallback. Return true Endpoint. |
| Dashboard Nodes | Servers.vue, Devices.vue, servers.ts, App.vue |
Nodes page, device→Node dropdown, sidebar. |
| Install Detection | main.go |
DB schema check on boot. -migrate-prod flag via goose. |
3. Phase 4.9: WGDashboard Parity
Goal: Multi-user, global device view, WG interface status, node health, Endpoint/ListenAddress split.
| Task | Files | What |
|---|---|---|
| User CRUD API | api/users.go, main.go |
GET/POST/DELETE /api/v1/users. Admin-only. |
| Admin Global Devices | api/devices.go |
Admin sees ALL devices. ?user_id= filter. |
| Users Page UI | Users.vue, users.ts, Devices.vue, App.vue, router/ |
Table, create/delete. Owner column for admin. User filter. |
| WG Local Manager | internal/wgmanager/manager.go |
GetStatus(), SetConfig(), Down(). Stub for Win. |
| WG API | main.go routes |
/wg/status, /wg/up, /wg/down. |
| WG Status Widget | WgStatusCard.vue on Dashboard |
ON/OFF toggle, Rx/Tx, peer count. |
| Node Health Check | main.go goroutine + api/servers.go |
UDP ping every 30s. GET /servers/status. |
| Endpoint/ListenAddress | models.go, servers.go, 001_init.sql, provisioning |
PublicEndpoint ≠ ListenAddress. Both editable. |
4. Phase 5.0: Peer Management + QR/Conf
Goal: Create peers directly from UI, QR codes, .conf download, share links.
Key Decisions:
- Route:
/api/v1/devices/:id/config(not/peers) - PresharedKey via
wgtypes.GeneratePrivateKey()for every direct peer RegTokenHash=""for direct peers (never provisionable via token)- Share links: Redis with
SHARE_LINK_TTLenv (default 24h) - AllowedIPs:
AllowInternet=true→0.0.0.0/0, else →<InternalIP>/32 - MUST NOT push peer config to kernel interface (deferred)
| Task | Files | What |
|---|---|---|
| PresharedKey in Provision | api/provisioning.go |
Generate PSK after device key. Include in ConfigPayload. |
| Direct Peer API | api/peers.go, main.go |
POST /api/v1/peers. Generate keys, alloc IP, create device. |
| Config Export | api/peers.go |
GET /devices/:id/config. WireGuard [Interface]+[Peer] text. |
| QR Code | api/peers.go (dep: go-qrcode) |
GET /devices/:id/qr. PNG 256x256. |
| Share Links | api/share.go |
POST /devices/:id/share + GET /share/:token (public). Redis-backed. |
| Add Peer Modal | AddPeerModal.vue, Devices.vue, api/peers.ts |
Form: Name, Server, AllowInternet. Success: QR + download + copy. |
| Config Modal | PeerConfigModal.vue, Devices.vue, DeviceDetail.vue |
QR, config text, download, share link, copy. |
| Share Page | ShareConfig.vue, router/index.ts |
Public route /share/:token. Shows expired for invalid. |
| Linked Devices | LinkedDevices.vue, DeviceDetail.vue |
Table: AllowedIPs, Online/Offline, LastHandshake, Disconnect. |
| Firewall Sync | api/peers.go |
Auto-create nftables rules for SSH (22) on peer create. Graceful on failure. |
5. Phase 5.5: WGDashboard Config Parity
Goal: Feature parity for WG config editing + peer settings + firewall sync.
DB Model Updates:
WgServer: Added MTU (1420), DNS (1.1.1.1)Device: Added EndpointAllowedIPs, DNS, MTU, PersistentKeepalive (25), Notes, IsSuspended, RxBytes, TxBytes
Backend API:
PUT /servers/:id— pointer fields for partial updates, firewall sync on port changePUT /devices/:id— pointer fields for new Device fieldsPOST /devices/:id/suspend,/unsuspend— toggle + firewall rule- Config generation: DNS/MTU/Keepalive cascade (device > server > default)
- Firewall sync on CreatePeer, Update (AllowedIPs change), Delete (cleanup)
Firewall Interface (existing):
AddForwardRule,RemoveForwardRule,AddInputRule,RemoveInputRule- Linux: via
nftCLI. Stub: no-op for non-Linux.
Frontend:
- Node Edit Modal —
PUT /servers/:idwith MTU, DNS, ListenPort - Advanced Peer Settings in DeviceDetail — accordion: AllowedIPs, DNS, MTU, Keepalive, Notes, Suspend toggle
- API client:
updateServer(),suspendDevice(),unsuspendDevice()
6. WireGuard VPN Functional Fix (wg-vpn-fix)
Goal: Fix 4 blocking gaps — server wg0 IP, peer sync, NAT/masquerade, device-agent TUN IP.
Scope: 10 files across server-core + device-agent.
| Fix | Files | What |
|---|---|---|
| Interface types | internal/wgmanager/manager.go |
Expanded interface + PeerConfig, PeerStatus types |
| Linux impl | wgmanager_linux.go |
ip addr add, SyncPeers() replace-all, NAT masquerade |
| Stub | wgmanager_stub.go |
Match new interface (no-op) |
| Handler wiring | api/wg.go, api/devices.go, api/peers.go, api/provisioning.go |
Sync peers on create/delete/suspend/provision |
| main.go | main.go |
Inject wgMgr to new handlers |
| Agent TUN IP | device-agent/.../wireguard.go, device-agent/main.go |
Assign InternalIP to TUN device |
| Tests | wgmanager/*_test.go, api/devices_test.go, api/wg_test.go |
Unit + integration |
Unchecked (environment blockers, not implementation):
- Dashboard build (needs submodule init)
- Full E2E on Linux (needs WireGuard kernel module)
- No duplicate nftables rules after restart toggle (needs Linux)
Key: NAT interface auto-detected via ip route show default, override via WG_NAT_INTERFACE env var.
7. Fix Gaps (nxg-fix-gaps)
Goal: Fix 6 gaps — stale artifacts, Dockerfiles, CI/CD, frontend tests, nftables Drop, crypto debt.
| Gap | What was done |
|---|---|
| Stale artifacts | Deleted connect_remote.txt (JWT+SSH creds), temp_section*.txt. Confirmed never in git history. |
| Dockerfiles | Created device-agent/Dockerfile (Go multi-stage). Pinned nginx:alpine → nginx:1.27-alpine. |
| Root CI/CD | Created .gitea/workflows/ci.yml — 3 jobs: server-core test, device-agent test, dashboard-ui build. |
| Frontend tests | Installed Vitest + vue-test-utils + happy-dom. Config + 2 test files (auth store 5 tests, modal 3 tests). 9/9 pass. |
| nftables Drop | Added conntrack rule (established/related accept). Changed forward policy Drop. Startup recovery loop (re-apply rules from DB). Auto input rule for WG port. |
| Crypto debt | Documented in server-core/AGENTS.md. 69 lines stdlib, 2 callers — keep duplicated. |
Files changed:
- Deleted:
connect_remote.txt,temp_section1.txt,temp_section2.txt - Modified:
package.json,index.html,dashboard-ui/Dockerfile,nftables_linux.go,main.go,models.go,AGENTS.md - Created:
device-agent/Dockerfile,.gitea/workflows/ci.yml,vitest.config.ts,hello.test.ts,add-peer-modal.spec.ts,auth-store.spec.ts
8. Fix Peers 500 + Delete Device
Goal: Fix HWID unique constraint violation (500 on POST /peers). Add delete button to Devices list.
Root Cause: Device.HWID string with uniqueIndex. Empty string "" collides → SQLSTATE 23505.
Fix: string → *string. NULLs are distinct in PostgreSQL unique constraints.
| Task | Files |
|---|---|
| Model change | internal/models/models.go:63 — HWID *string |
| CreatePeer fix | api/peers.go — remove HWID: "" (nil is correct) |
| Delete button | src/views/Devices.vue — add Delete to Actions column |
| SQL migration | SSH: UPDATE devices SET hwid = NULL WHERE hwid = '' |
9. Server-Core Multi-Stage Docker Build
Goal: Single-stage → multi-stage Dockerfile. Clean up orphan binaries.
| Task | Files |
|---|---|
| Dockerfile rewrite | apps/server-core/Dockerfile — builder (golang:1.25) + prod (alpine:3.20) |
| .dockerignore | apps/server-core/.dockerignore — exclude build artifacts |
| .gitignore update | apps/server-core/.gitignore — server-core* glob |
| Untrack binary | git rm --cached server-core-linux |
| Delete binaries | 3 files deleted: server-core, server-core-linux, bin/server-core |
Key: Dev compose uses target: builder with volume mount at /app for air hot-reload.
10. Deployment Tools (nxg-deployment-tools)
Goal: Makefile + HWID migration on live server.
| Task | What |
|---|---|
| Makefile | up, down, reset-db, logs, dev, migrate targets. |
| HWID migration | SSH to 172.20.8.191: SQL UPDATE...SET hwid=NULL WHERE hwid=''. Restart container. |
11. AGENTS.md Generation (agents-deep-init)
Goal: Generate hierarchical AGENTS.md for root + 3 submodules.
| File | Lines |
|---|---|
./AGENTS.md |
Root — 109 lines: project overview, structure, code map, conventions, anti-patterns |
apps/server-core/AGENTS.md |
61 lines: Go backend reference |
apps/dashboard-ui/AGENTS.md |
58 lines: Vue 3 frontend reference |
apps/device-agent/AGENTS.md |
50 lines: Go stealth daemon reference |
Key Anti-Patterns (Project-wide "Must NOT do")
- NEVER
nft flush table— only atomic add/remove - NEVER log plaintext or encryption keys
- NEVER reopen completed phases/commits — fix forward only
- NEVER rebuild
shared/crypto/encryptor.go— copy identical file (known debt) - NEVER commit build artifacts (binaries,
dist/) - NEVER force push
- NEVER create cross-phase workarounds
- NEVER change nftables input chain default (WG UDP listener needs Accept)
- NEVER write WireGuard config to
/etc/wireguard/(stealth invariant) - NEVER use
docker-compose(v1) — alwaysdocker compose(v2)
Files Modified (Master List)
server-core/ (Go/Gin backend)
main.go, models.go, api/servers.go, api/devices.go, api/peers.go, api/provisioning.go, api/wg.go, api/users.go, api/share.go, api/auth.go, internal/firewall/nftables_linux.go, internal/firewall/nftables_stub.go, internal/firewall/manager.go, internal/wgmanager/manager.go, internal/wgmanager/wgmanager_linux.go, internal/wgmanager/wgmanager_stub.go, internal/ipam/manager.go, internal/models/models.go, migrations/001_init.sql, AGENTS.md, Dockerfile, .dockerignore, .gitignore
dashboard-ui/ (Vue 3 frontend)
package.json, index.html, vitest.config.ts, Dockerfile, src/views/Servers.vue, src/views/Devices.vue, src/views/Users.vue, src/views/DeviceDetail.vue, src/views/ShareConfig.vue, src/views/Dashboard.vue, src/components/AddPeerModal.vue, src/components/PeerConfigModal.vue, src/components/LinkedDevices.vue, src/components/WgStatusCard.vue, src/api/peers.ts, src/api/servers.ts, src/api/users.ts, src/api/devices.ts, src/stores/auth.ts, src/router/index.ts, src/App.vue
device-agent/ (Go stealth daemon)
main.go, Dockerfile, internal/tunnel/wireguard.go
Root
Makefile, .gitea/workflows/ci.yml, AGENTS.md
Deleted (stale artifacts)
connect_remote.txt, temp_section1.txt, temp_section2.txt, apps/server-core/server-core, apps/server-core/server-core-linux, apps/server-core/bin/server-core