# PROJECT KNOWLEDGE BASE **Generated:** 2026-06-20 **Branch:** `main` ## OVERVIEW NexusGuard SD-WAN Suite: Enterprise Zero-Trust SD-WAN with WireGuard tunneling, centralized IPAM, and real-time nftables network isolation. Monorepo with 3 git submodules: Go backend (Gin), Vue 3 dashboard, Go device agent. ## TOPOLOGY | Host | SSH | Role | |------|-----|------| | Production server | `root@172.20.8.191` | Runs server-core, Postgres, Redis, nginx, nftables, WireGuard | | Gitea server | `root@172.20.8.92` | Private Git hosting (`git.datadunia.com`) | - Server project folder: `/root/Nexus-Guard-Suite` - Deploy: `./update.sh` (don't build manually) - Actual WireGuard wg0 IP: `10.172.21.1/24` (on server 172.20.8.191) - Agent WG IPs: dynamic from pool `10.172.21.0/24` ## STRUCTURE ``` ./ ├── apps/ │ ├── server-core/ # Go/Gin API backend (submodule) │ ├── dashboard-ui/ # Vue 3 + Vite frontend (submodule) │ └── device-agent/ # Go stealth daemon + system tray (submodule) ├── docker-compose.yml # Production orchestration ├── docker-compose.dev.yml# Dev (air hot-reload) ├── Makefile # up/down/dev/migrate/reset-db ├── update.sh # Docker update: auto-generate .env + pull/build/migrate ├── nexusguard-install.sh # Native install (systemd + nginx) ├── nexusguard-uninstall.sh ├── .env.example # DB/JWT/SALT/VITE config template ├── .gitmodules # 3 submodules → git.datadunia.com └── .opencode/ # IDE agent config (tooling, not project code) ``` **CRITICAL**: `apps/*` are **git submodules** — clone with `--recurse-submodules`. ## WHERE TO LOOK | Task | Location | Notes | |------|----------|-------| | API handlers | `apps/server-core/api/` | 17 files: auth, devices, peers, rules, share, provisioning, servers, wg | | Backend core | `apps/server-core/internal/` | auth, config, firewall, heartbeat, ipam, models, wgmanager | | Dev migration | `apps/server-core/main_dev.go` | GORM AutoMigrate (build tag `dev`) | | Firewall rules | `apps/server-core/internal/firewall/` | nftables Linux rules | | gRPC signaling | `apps/server-core/signaling/` | Manager + Server: gRPC session tracking, Connect handler, recv loop | | Dashboard views | `apps/dashboard-ui/src/views/` | Vue SFC pages | | Dashboard API client | `apps/dashboard-ui/src/api/` | Axios API modules | | Dashboard stores | `apps/dashboard-ui/src/stores/` | Pinia state stores | | Agent client | `apps/device-agent/internal/client/` | Provisioning + heartbeat | | Agent signaling | `apps/device-agent/internal/signaling/` | gRPC connect with fallback + reconnect | | Agent tunnel | `apps/device-agent/internal/tunnel/` | Memory-injected WireGuard | | Shared crypto | `apps/*/shared/crypto/encryptor.go` | AES-256-GCM (duplicated identical) | | CI workflows | `apps/*/.gitea/workflows/build.yml` | Gitea Actions per submodule | | Build config | `apps/dashboard-ui/vite.config.ts` | Vite 8 + Vue + TailwindCSS v4 | | Source of truth | `apps/server-core/docs/` | API_SPEC, KEY_ROTATION, PEER_DISCOVERY | ## SIGNALING ARCHITECTURE (CRITICAL) ### Topology ``` ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ │ Dashboard │──HTTP──▶│ Server Core │◀─WG────▶│ Device Agent │ │ (Vue 3) │ :8080 │ (Go/Gin) │ :51820 │ (Go) │ └──────────────┘ │ │ └──────────────┘ │ Port 8080: │ │ │ - HTTP API │ ┌────┴────┐ │ - gRPC Signal │ │ TUN (wg)│ │ (cmux) │ │ Memory │ └─────────────────┘ └─────────┘ ``` ### Transport Fallback Chain (Agent → Server) 1. gRPC via HTTPS domain (TLS) → `italy-twenty.gl.at.ply.gg:443` 2. gRPC via WireGuard IP (insecure, tunnel-encrypted) → `10.172.21.1:8080` 3. HTTP heartbeat (fallback) → `serverURL/api/v1/heartbeat` ### Heartbeat = PRIMARY Channel Always runs. Handles: - Health check (30s interval) - Config sync (detects config changes → rebuild tunnel) - Handshake monitoring (rebuilds tunnel if lastHandshake > 120s) - Recovery after failure (wasFailing → OnRecovered → full rebuild) ### gRPC = BONUS Channel Best-effort. Handles: - Real-time commands: Suspend, Resume, ConfigUpdate, Reconnect, Disconnect - StatusReport from agent (tunnel_up, lastHandshake, state) - Ping/Pong keepalive (20s) ### gRPC Port Multiplexing HTTP + gRPC share port 8080 via `cmux`: - Server: `cmux.New(lis)` → match gRPC by `content-type` header, match HTTP by `Any()` - Agent connects to same port for both HTTP API and gRPC ### Key Design Decisions - Agent NEVER destroys tunnel on heartbeat failure — only rebuilds - `OnFailure = log only`, `OnRecovered = full rebuild` - gRPC OnDisconnect/OnGRPCFailed just log — heartbeat continues - Heartbeat reads `last_handshake_time_sec` from WG IPC to detect stale tunnel - gRPC StatusReport sends handshake age to server every 30s - Server WG IP read from actual kernel interface (`net.InterfaceByName`), NOT from stale DB ### Agent Connection Lifecycle 1. Provision → register with server, get WireGuard config 2. Start tunnel (memory-injected, no disk files) 3. Start heartbeat (always, primary channel) 4. Start gRPC (if ServerWGIP available, bonus channel) 5. On heartbeat config change → rebuild tunnel 6. On heartbeat stale handshake → rebuild tunnel 7. On heartbeat failure+recovery → rebuild tunnel 8. On gRPC suspend → stop tunnel, heartbeat continues 9. On gRPC resume → rebuild tunnel from server config ### Protobuf Messages - **Agent → Server**: HelloMessage, HeartbeatAck, StatusReport, PingMessage - **Server → Agent**: ConfigUpdate, SuspendCommand, ResumeCommand, ReconnectCommand, DisconnectCommand, KeepAlive, PongMessage ## CODE MAP | Symbol | Type | Location | Role | |--------|------|----------|------| | `main()` (server-core) | func | `apps/server-core/main.go` | Entry: CLI flags + Gin init | | `main()` (device-agent) | func | `apps/device-agent/main.go` | Entry: systray + agent daemon lifecycle | | `onReady()` / `onExit()` | func | `apps/device-agent/main.go` | System tray setup and cleanup | | `startAgent()` / `stopAgent()` | func | `apps/device-agent/main.go` | Agent connect/disconnect lifecycle | | `generateIcon()` | func | `apps/device-agent/icon.go` | 16x16 shield icon for tray | | `config.Load()` | func | `apps/server-core/internal/config/` | Env-based config loader | | `config.LoadConfFile()` | func | `apps/server-core/internal/config/config_loader.go` | Config file parser (.env / nexusguard.conf) | | `auth.Init()` | func | `apps/server-core/internal/auth/` | JWT sign/verify init | | `firewall.InitNetwork()` | func | `apps/server-core/internal/firewall/` | nftables table/set creation | | `ipam.AllocateIP()` | func | `apps/server-core/internal/ipam/` | IP pool allocation from CIDR | | `wgmanager.SetConfig()` | func | `apps/server-core/internal/wgmanager/` | WireGuard config push | | `wgmanager.GetInterfaceAddress()` | func | `apps/server-core/internal/wgmanager/` | Read actual WG interface IP from kernel | | `models.AutoMigrate()` | func | `apps/server-core/internal/models/` | GORM schema migration | | `encrypt()` / `decrypt()` | func | `apps/*/shared/crypto/encryptor.go` | AES-256-GCM (identical) | | `StartHeartbeat()` | func | `apps/device-agent/internal/client/heartbeat.go` | Heartbeat loop + handshake monitoring | | `checkHandshake()` | func | `apps/device-agent/internal/client/heartbeat.go` | Read WG IPC handshake time | | `ConnectAndRun()` | func | `apps/device-agent/internal/signaling/client.go` | gRPC connect with fallback + reconnect | | `statusLoop()` | func | `apps/device-agent/internal/signaling/client.go` | Sends StatusReport every 30s | | `NewManager()` | func | `apps/server-core/signaling/manager.go` | gRPC session tracking | | `NewServer()` | func | `apps/server-core/signaling/server.go` | gRPC Connect handler + recv loop | ## CONVENTIONS - **Go**: Standard layout (`main.go` in root, `internal/`, `api/`) - **Vue 3**: Composition API + `