docs(root): comprehensive AGENTS.md with signaling architecture, topology, lifecycle

This commit is contained in:
datadunia
2026-06-20 07:46:38 +07:00
parent d7bbbbdefd
commit b447ad9757
+86 -7
View File
@@ -1,11 +1,22 @@
# PROJECT KNOWLEDGE BASE # PROJECT KNOWLEDGE BASE
**Generated:** 2026-05-22 **Generated:** 2026-06-20
**Commit:** `92051d5`
**Branch:** `main` **Branch:** `main`
## OVERVIEW ## 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 ## STRUCTURE
``` ```
@@ -20,7 +31,7 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin
├── setup.sh # First-run: generate .env + random keys ├── setup.sh # First-run: generate .env + random keys
├── update.sh # Docker update: pull/build/migrate ├── update.sh # Docker update: pull/build/migrate
├── nexusguard-install.sh # Native install (systemd + nginx) ├── nexusguard-install.sh # Native install (systemd + nginx)
├── nexusguard-uninstall.sh # Native uninstall ├── nexusguard-uninstall.sh
├── .env.example # DB/JWT/SALT/VITE config template ├── .env.example # DB/JWT/SALT/VITE config template
├── .gitmodules # 3 submodules → git.datadunia.com ├── .gitmodules # 3 submodules → git.datadunia.com
└── .opencode/ # IDE agent config (tooling, not project code) └── .opencode/ # IDE agent config (tooling, not project code)
@@ -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 | | 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`) | | Dev migration | `apps/server-core/main_dev.go` | GORM AutoMigrate (build tag `dev`) |
| Firewall rules | `apps/server-core/internal/firewall/` | nftables Linux rules | | 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 views | `apps/dashboard-ui/src/views/` | Vue SFC pages |
| Dashboard API client | `apps/dashboard-ui/src/api/` | Axios API modules | | Dashboard API client | `apps/dashboard-ui/src/api/` | Axios API modules |
| Dashboard stores | `apps/dashboard-ui/src/stores/` | Pinia state stores | | Dashboard stores | `apps/dashboard-ui/src/stores/` | Pinia state stores |
| Agent client | `apps/device-agent/internal/client/` | Provisioning + heartbeat | | 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 | | Agent tunnel | `apps/device-agent/internal/tunnel/` | Memory-injected WireGuard |
| Shared crypto | `apps/*/shared/crypto/encryptor.go` | AES-256-GCM (duplicated identical) | | Shared crypto | `apps/*/shared/crypto/encryptor.go` | AES-256-GCM (duplicated identical) |
| CI workflows | `apps/*/.gitea/workflows/build.yml` | Gitea Actions per submodule | | CI workflows | `apps/*/.gitea/workflows/build.yml` | Gitea Actions per submodule |
| Build config | `apps/dashboard-ui/vite.config.ts` | Vite 8 + Vue + TailwindCSS v4 | | 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 | | 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 ## CODE MAP
| Symbol | Type | Location | Role | | 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 | | `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 | | `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.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 | | `models.AutoMigrate()` | func | `apps/server-core/internal/models/` | GORM schema migration |
| `encrypt()` / `decrypt()` | func | `apps/*/shared/crypto/encryptor.go` | AES-256-GCM (identical) | | `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 ## CONVENTIONS
- **Go**: Standard layout (`main.go` in root, `internal/`, `api/`) - **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) ## ANTI-PATTERNS (THIS PROJECT)
- **NEVER** `nft flush table` — only atomic add/remove - **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** log plaintext or encryption keys
- **NEVER** reopen completed phases/commits — fix forward only - **NEVER** reopen completed phases/commits — fix forward only
- **NEVER** rebuild `shared/crypto/encryptor.go` — copy identical file - **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` - Go versions diverge: server-core `1.25.7`, device-agent `1.25.1`
- No root linter configs (`.golangci.yml`, `.eslintrc`, `.editorconfig`) - No root linter configs (`.golangci.yml`, `.eslintrc`, `.editorconfig`)
- Shell scripts use deprecated `docker-compose` v1, Makefile uses `docker compose` v2 - 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) - `package.json` name is `"temp-ui"` (stale scaffold remnant)