Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f98eedbfbe | |||
| ea6fbe83b5 | |||
| 91ed1adcda | |||
| 95ec25b80e | |||
| 2738e3c7d1 | |||
| 80c97bbc22 | |||
| 077384c433 | |||
| fc204bba17 | |||
| e1e35318de | |||
| bd79bb0043 | |||
| 248ad123bb | |||
| 855fb2dbee | |||
| 45f95a957a | |||
| e50c1900ad | |||
| 957cc8d5cb | |||
| da94cc3f6b | |||
| 465b1f7388 | |||
| e9e4db261d | |||
| 6f6b44b8a2 | |||
| 1a1f8a5a8a | |||
| 61a5936224 | |||
| f03686ac3d | |||
| e1cbdb66c8 | |||
| a89fdaf435 | |||
| 633e06f556 | |||
| b447ad9757 | |||
| d7bbbbdefd | |||
| b1458d3a99 | |||
| 2c5a3c7fa5 | |||
| 5a6675c70a | |||
| 4528493647 | |||
| 9f1e37fc57 | |||
| 5e8b72efdc | |||
| 08b74ce45b | |||
| 7c5604d510 | |||
| 26e65006b5 | |||
| c011215deb | |||
| 6f5ffb8b1d | |||
| 934d4a03d7 | |||
| 27c003864e | |||
| 014309c390 | |||
| d5b1f4428a | |||
| 6c5a762407 | |||
| 9c8a3c0751 | |||
| 272d744e12 | |||
| 5304bd11ca | |||
| 359a23c079 | |||
| facd8bd7d5 | |||
| 83722abd32 | |||
| 359bd6f9c4 | |||
| e81c1dd448 | |||
| 9433dc3547 | |||
| 4d58b01641 | |||
| 4e19c371d0 | |||
| aa620dec6a | |||
| 48340aa7a3 | |||
| 68ebfbaad3 | |||
| 46ff7ca737 |
@@ -59,7 +59,7 @@ jobs:
|
|||||||
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
|
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
|
||||||
OUT="bin/nexus-device-agent-${GOOS}-${GOARCH}${EXT}"
|
OUT="bin/nexus-device-agent-${GOOS}-${GOARCH}${EXT}"
|
||||||
echo "Building $OUT ..."
|
echo "Building $OUT ..."
|
||||||
CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build -o "$OUT" .
|
CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" go build -ldflags="-s -w" -o "$OUT" .
|
||||||
done
|
done
|
||||||
echo "=== Build output ==="
|
echo "=== Build output ==="
|
||||||
ls -la bin/
|
ls -la bin/
|
||||||
|
|||||||
@@ -35,9 +35,18 @@ jobs:
|
|||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
id: cache-build
|
id: cache-build
|
||||||
with:
|
with:
|
||||||
path: apps/server-core/bin
|
path: |
|
||||||
|
apps/server-core/bin
|
||||||
|
apps/server-core/docs
|
||||||
key: build-server-core-${{ steps.hash-src.outputs.hash }}
|
key: build-server-core-${{ steps.hash-src.outputs.hash }}
|
||||||
|
|
||||||
|
- name: Generate Swagger docs
|
||||||
|
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||||
|
working-directory: apps/server-core
|
||||||
|
run: |
|
||||||
|
go install github.com/swaggo/swag/cmd/swag@v1.16.6
|
||||||
|
swag init -g main.go --parseDependency --parseInternal
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||||
working-directory: apps/server-core
|
working-directory: apps/server-core
|
||||||
|
|||||||
+27
-47
@@ -11,7 +11,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# TEST TAGS — test only, no build, no docs
|
# TESTS — test tags only
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
server-core-test:
|
server-core-test:
|
||||||
if: contains(gitea.ref_name, 'test')
|
if: contains(gitea.ref_name, 'test')
|
||||||
@@ -76,67 +76,47 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
# DEV/BETA TAGS — build only, no docs, no test
|
# BUILD — after tests pass (release) or directly (dev/beta)
|
||||||
# ====================================================================
|
# ====================================================================
|
||||||
build-server-core-dev:
|
build-server-core:
|
||||||
if: contains(gitea.ref_name, 'dev') || contains(gitea.ref_name, 'beta')
|
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
|
||||||
|
needs: [server-core-test, device-agent-test, dashboard-test]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
uses: ./.gitea/workflows/build_server_core.yaml
|
uses: ./.gitea/workflows/build_server_core.yaml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-device-agent-dev:
|
build-device-agent:
|
||||||
if: contains(gitea.ref_name, 'dev') || contains(gitea.ref_name, 'beta')
|
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
|
||||||
|
needs: [server-core-test, device-agent-test, dashboard-test]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
uses: ./.gitea/workflows/build_device_agent.yaml
|
uses: ./.gitea/workflows/build_device_agent.yaml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-dashboard-dev:
|
build-dashboard:
|
||||||
if: contains(gitea.ref_name, 'dev') || contains(gitea.ref_name, 'beta')
|
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
|
||||||
|
needs: [server-core-test, device-agent-test, dashboard-test]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
uses: ./.gitea/workflows/build_dashboard.yaml
|
uses: ./.gitea/workflows/build_dashboard.yaml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
release-dev:
|
# ====================================================================
|
||||||
if: (contains(gitea.ref_name, 'dev') || contains(gitea.ref_name, 'beta')) && !failure()
|
# DOCS — after builds pass, release tags only
|
||||||
needs: [build-server-core-dev, build-device-agent-dev, build-dashboard-dev]
|
# ====================================================================
|
||||||
|
build-docs:
|
||||||
|
if: always() && !contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
|
||||||
|
needs: [build-server-core, build-device-agent, build-dashboard]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
uses: ./.gitea/workflows/docs_call.yaml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# RELEASE — after everything passes
|
||||||
|
# ====================================================================
|
||||||
|
release:
|
||||||
|
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
|
||||||
|
needs: [build-server-core, build-device-agent, build-dashboard, build-docs]
|
||||||
uses: ./.gitea/workflows/release_call.yaml
|
uses: ./.gitea/workflows/release_call.yaml
|
||||||
with:
|
with:
|
||||||
prerelease: ${{ contains(gitea.ref_name, 'dev') }}
|
prerelease: ${{ contains(gitea.ref_name, 'dev') }}
|
||||||
draft: ${{ contains(gitea.ref_name, 'beta') }}
|
draft: ${{ contains(gitea.ref_name, 'beta') }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
# ====================================================================
|
|
||||||
# RELEASE TAGS — build all + docs + release
|
|
||||||
# ====================================================================
|
|
||||||
build-server-core-release:
|
|
||||||
if: "!contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test')"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
uses: ./.gitea/workflows/build_server_core.yaml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
build-device-agent-release:
|
|
||||||
if: "!contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test')"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
uses: ./.gitea/workflows/build_device_agent.yaml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
build-dashboard-release:
|
|
||||||
if: "!contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test')"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
uses: ./.gitea/workflows/build_dashboard.yaml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
build-docs-release:
|
|
||||||
if: "!contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test')"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
uses: ./.gitea/workflows/docs_call.yaml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
release-stable:
|
|
||||||
if: "!contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test') && !failure()
|
|
||||||
needs: [build-server-core-release, build-device-agent-release, build-dashboard-release, build-docs-release]
|
|
||||||
uses: ./.gitea/workflows/release_call.yaml
|
|
||||||
with:
|
|
||||||
prerelease: false
|
|
||||||
draft: false
|
|
||||||
secrets: inherit
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ temp_*
|
|||||||
# Build output
|
# Build output
|
||||||
dist/
|
dist/
|
||||||
/dist/
|
/dist/
|
||||||
|
.tests/
|
||||||
|
tests/
|
||||||
|
|
||||||
# OpenCode
|
# OpenCode
|
||||||
connect_remote.txt
|
connect_remote.txt
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
@@ -15,12 +26,12 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin
|
|||||||
│ ├── dashboard-ui/ # Vue 3 + Vite frontend (submodule)
|
│ ├── dashboard-ui/ # Vue 3 + Vite frontend (submodule)
|
||||||
│ └── device-agent/ # Go stealth daemon + system tray (submodule)
|
│ └── device-agent/ # Go stealth daemon + system tray (submodule)
|
||||||
├── docker-compose.yml # Production orchestration
|
├── docker-compose.yml # Production orchestration
|
||||||
├── docker-compose.dev.yml # Dev (air hot-reload)
|
├── docker-compose.dev.yml# Dev (air hot-reload)
|
||||||
├── Makefile # up/down/dev/migrate/reset-db
|
├── Makefile # up/down/dev/migrate/reset-db
|
||||||
├── 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
|
||||||
@@ -200,10 +280,43 @@ go run -tags dev ./apps/server-core -create-admin -user admin -pass "..."
|
|||||||
sudo /usr/local/bin/nexusguard-server -create-admin -user admin -pass "..."
|
sudo /usr/local/bin/nexusguard-server -create-admin -user admin -pass "..."
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## heartbeat server <-> device-agent
|
||||||
|
|
||||||
|
Konsep yang Benar
|
||||||
|
Heartbeat = satu konsep, tiga jalur transport:
|
||||||
|
|
||||||
|
0 Transport Protocol Endpoint Kapan Dipakai
|
||||||
|
1 gRPC via domain (HTTPS proxy) gRPC bidi stream api-nexus.datadunia.com:443 Pertama dicoba
|
||||||
|
2 gRPC via WG IP (direct) gRPC bidi stream 10.172.21.1:8080 Fallback jika proxy swallowed
|
||||||
|
3 HTTP API REST POST /api/v1/heartbeat Fallback terakhir / always running
|
||||||
|
Satu konsep yang sama: kirim config_hash + last_handshake + tunnel_up → server compare → respond dengan config jika berubah.
|
||||||
|
|
||||||
|
Yang Perlu Diperbaiki
|
||||||
|
client.go: Perlu ada gRPC heartbeat loop (kirim HeartbeatRequest via stream periodik) + handle HeartbeatResponse
|
||||||
|
heartbeat.go: HTTP heartbeat tetap ada sebagai fallback
|
||||||
|
Transport switching: Saat gRPC connected → heartbeat via gRPC. Saat gRPC disconnected → heartbeat via HTTP
|
||||||
|
handler.go: Perlu handleHeartbeatResponse untuk process config dari gRPC heartbeat
|
||||||
|
Server manager.go: Perlu sendMu untuk prevent concurrent stream.Send()
|
||||||
|
Alur yang Benar (setelah perbaikan)
|
||||||
|
Agent Start
|
||||||
|
→ Provision (HTTP) → config pertama dari HTTP API → build tunnel
|
||||||
|
→ Start HTTP heartbeat (always running, fallback transport)
|
||||||
|
→ Start gRPC (HTTPS → WG IP)
|
||||||
|
|
||||||
|
gRPC Connected:
|
||||||
|
→ establishStream: kirim HelloMessage → terima ConfigUpdate (verify only, jangan rebuild)
|
||||||
|
→ heartbeatLoop: kirim HeartbeatRequest via gRPC setiap 30s
|
||||||
|
→ Server respond: HeartbeatResponse (config_changed? → rebuild via handler)
|
||||||
|
→ dispatch: handle Suspend/Resume/ConfigUpdate/Reconnect/Disconnect
|
||||||
|
|
||||||
|
gRPC Disconnected:
|
||||||
|
→ HTTP heartbeat continues (unaffected)
|
||||||
|
→ gRPC reconnect loop
|
||||||
|
→ When reconnected → switch heartbeat back to gRPC
|
||||||
|
|
||||||
## NOTES
|
## NOTES
|
||||||
- Submodules → private Gitea (`git.datadunia.com`); CI via Gitea Actions per submodule
|
- Submodules → private Gitea (`git.datadunia.com`); CI via Gitea Actions per submodule
|
||||||
- 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)
|
||||||
|
|||||||
+1
-1
Submodule apps/dashboard-ui updated: dc20427f97...f1e6126f7d
+1
-1
Submodule apps/device-agent updated: e64fd34904...cd3ea79ce7
+1
-1
Submodule apps/server-core updated: d8ab3cbca2...decb2e47bf
Reference in New Issue
Block a user