From b447ad9757861e3536347347f3ca8f8c1227568a Mon Sep 17 00:00:00 2001 From: datadunia Date: Sat, 20 Jun 2026 07:46:38 +0700 Subject: [PATCH] docs(root): comprehensive AGENTS.md with signaling architecture, topology, lifecycle --- AGENTS.md | 117 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c88ee34..4970ed1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,29 +1,40 @@ # PROJECT KNOWLEDGE BASE -**Generated:** 2026-05-22 -**Commit:** `92051d5` +**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. +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 -├── setup.sh # First-run: generate .env + random keys -├── update.sh # Docker update: pull/build/migrate -├── nexusguard-install.sh # Native install (systemd + nginx) -├── nexusguard-uninstall.sh # Native uninstall -├── .env.example # DB/JWT/SALT/VITE config template -├── .gitmodules # 3 submodules → git.datadunia.com -└── .opencode/ # IDE agent config (tooling, not project code) +│ ├── 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 +├── setup.sh # First-run: generate .env + random keys +├── update.sh # Docker update: 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`. @@ -35,16 +46,78 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin | 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 | -| Plan guardrails | `.sisyphus/plans/` | Anti-patterns, "Must NOT do" rules | + +## 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 | @@ -60,8 +133,15 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin | `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/`) @@ -79,7 +159,7 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin ## ANTI-PATTERNS (THIS PROJECT) - **NEVER** `nft flush table` — only atomic add/remove -- **NEVER** commit temp/debug/test files (`nft-fix.sh`, `temp_*.txt` etc) in project root use ./tests folder and dont commit +- **NEVER** commit temp/debug/test files (`nft-fix.sh`, `temp_*.txt` etc) in project root; use `./tests` folder - **NEVER** log plaintext or encryption keys - **NEVER** reopen completed phases/commits — fix forward only - **NEVER** rebuild `shared/crypto/encryptor.go` — copy identical file @@ -205,5 +285,4 @@ sudo /usr/local/bin/nexusguard-server -create-admin -user admin -pass "..." - Go versions diverge: server-core `1.25.7`, device-agent `1.25.1` - No root linter configs (`.golangci.yml`, `.eslintrc`, `.editorconfig`) - Shell scripts use deprecated `docker-compose` v1, Makefile uses `docker compose` v2 -- Root has stale artifacts: `connect_remote.txt`, `temp_section*.txt` - `package.json` name is `"temp-ui"` (stale scaffold remnant)