# 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_TTL` env (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 change
- `PUT /devices/:id` — pointer fields for new Device fields
- `POST /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 `nft` CLI. Stub: no-op for non-Linux.

**Frontend**:
- Node Edit Modal — `PUT /servers/:id` with 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) — always `docker 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`
