Compare commits

..

52 Commits

Author SHA1 Message Date
datadunia a485c8df42 fix(ci): add cache-dependency-path to setup-go (fix go.sum not found warning)
CI / server-core-test (push) Has been skipped
CI / device-agent-test (push) Has been skipped
CI / dashboard-test (push) Has been skipped
CI / build-server-core (push) Successful in 20m44s
CI / build-device-agent (push) Successful in 2m10s
CI / build-dashboard (push) Successful in 1m42s
CI / build-docs (push) Has been skipped
CI / release (push) Successful in 24s
2026-06-21 13:44:58 +07:00
datadunia f98eedbfbe chore: update device-agent submodule
CI / server-core-test (push) Has been skipped
CI / device-agent-test (push) Has been skipped
CI / dashboard-test (push) Has been skipped
CI / build-server-core (push) Successful in 10m19s
CI / build-device-agent (push) Successful in 6m20s
CI / build-dashboard (push) Successful in 4m30s
CI / build-docs (push) Successful in 4m6s
CI / release (push) Successful in 29s
2026-06-21 13:28:02 +07:00
datadunia ea6fbe83b5 chore: update dashboard-ui submodule ref 2026-06-21 13:24:44 +07:00
datadunia 91ed1adcda chore: update dashboard-ui submodule ref 2026-06-21 13:20:24 +07:00
datadunia 95ec25b80e chore: update dashboard-ui submodule ref 2026-06-21 13:06:14 +07:00
datadunia 2738e3c7d1 chore: update dashboard-ui submodule ref 2026-06-21 05:44:21 +07:00
datadunia 80c97bbc22 chore: update dashboard-ui submodule ref 2026-06-21 05:32:13 +07:00
datadunia 077384c433 chore: update dashboard-ui submodule ref 2026-06-21 05:29:17 +07:00
datadunia fc204bba17 chore: update device-agent submodule to b1c1576 2026-06-21 01:24:52 +07:00
datadunia e1e35318de chore: update submodules — agent 65714b5, server decb2e4
agent: console X survives, UTF-8 encoding, debug-gated heartbeat
server: reject heartbeat for suspended devices (403)
2026-06-21 01:16:10 +07:00
datadunia bd79bb0043 chore: update device-agent submodule to 05b5e5c 2026-06-21 00:52:50 +07:00
datadunia 248ad123bb chore: update device-agent submodule to 1ab8304 (console X fix) 2026-06-21 00:14:25 +07:00
datadunia 855fb2dbee chore: update device-agent submodule to 71a2be5 (crash fix + config redaction) 2026-06-21 00:05:17 +07:00
datadunia 45f95a957a chore: update device-agent submodule to e93ca27 (log console + WG redaction) 2026-06-20 23:32:04 +07:00
datadunia e50c1900ad chore: update device-agent submodule to 9cfd127 2026-06-20 19:43:11 +07:00
datadunia 957cc8d5cb chore: update device-agent submodule 2026-06-20 18:33:42 +07:00
datadunia da94cc3f6b chore: update AGENTS.md + device-agent submodule 2026-06-20 18:32:11 +07:00
datadunia 465b1f7388 chore: update submodule refs 2026-06-20 18:16:22 +07:00
datadunia e9e4db261d chore: update device-agent submodule 2026-06-20 17:56:16 +07:00
datadunia 6f6b44b8a2 chore: update submodule refs 2026-06-20 17:48:10 +07:00
datadunia 1a1f8a5a8a chore: update submodule refs 2026-06-20 17:14:47 +07:00
datadunia 61a5936224 chore: update submodule refs 2026-06-20 14:38:15 +07:00
datadunia f03686ac3d chore: update device-agent submodule (assign_ip_other fix) 2026-06-20 10:39:46 +07:00
datadunia e1cbdb66c8 fix: update submodule pointers (port forward push + debug logging) 2026-06-20 10:30:35 +07:00
datadunia a89fdaf435 chore: update submodule refs 2026-06-20 09:36:23 +07:00
datadunia 633e06f556 chore: update submodule pointers (docs: AGENTS.md updates) 2026-06-20 07:56:18 +07:00
datadunia b447ad9757 docs(root): comprehensive AGENTS.md with signaling architecture, topology, lifecycle 2026-06-20 07:46:38 +07:00
datadunia d7bbbbdefd chore: update submodule refs 2026-06-20 06:53:20 +07:00
datadunia b1458d3a99 chore: update device-agent submodule (debug logging)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-20 05:06:04 +07:00
datadunia 2c5a3c7fa5 chore: update device-agent submodule (heartbeat primary)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-20 04:53:03 +07:00
datadunia 5a6675c70a chore: update submodules (gRPC keepalive + 3-level fallback)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-19 21:01:52 +07:00
datadunia 4528493647 chore: update device-agent submodule (transport memory)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-19 20:22:06 +07:00
datadunia 9f1e37fc57 chore: update device-agent submodule (gRPC fallback)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-19 20:18:24 +07:00
datadunia 5e8b72efdc chore: update device-agent submodule (insecure gRPC fix)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-19 20:06:12 +07:00
datadunia 08b74ce45b chore: update server-core submodule ref 2026-06-19 19:48:17 +07:00
datadunia 7c5604d510 chore: update submodule refs (gRPC multiplex on port 8080)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-19 18:08:46 +07:00
datadunia 26e65006b5 chore: update submodule refs (gRPC port fix + server_wg_ip)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-19 17:50:52 +07:00
datadunia c011215deb chore: update submodule refs (gRPC signaling added) 2026-06-19 12:25:47 +07:00
datadunia 6f5ffb8b1d chore: update dashboard-ui submodule ref 2026-06-19 09:08:44 +07:00
datadunia 934d4a03d7 chore: update dashboard-ui submodule ref 2026-06-19 08:49:30 +07:00
datadunia 27c003864e chore: update submodule refs 2026-06-19 08:03:41 +07:00
datadunia 014309c390 fix(ci): strip debug symbols from device-agent release build
- CI build: add -ldflags='-s -w' to reduce binary size ~30%
- Update device-agent submodule reference (heartbeat fixes)
2026-06-18 20:07:13 +07:00
datadunia d5b1f4428a chore: update submodule refs 2026-06-18 18:36:42 +07:00
datadunia 6c5a762407 feat: EndpointAllowedIPs in provisioning/heartbeat, agent uses server AllowedIPs 2026-06-18 16:08:47 +07:00
datadunia 9c8a3c0751 fix: heartbeat config change detection, device_id, DNS, tunnel rebuild timing 2026-06-18 14:44:14 +07:00
datadunia 272d744e12 chore: update submodule refs 2026-06-18 14:24:51 +07:00
datadunia 5304bd11ca chore: update device-agent + server-core submodule refs 2026-06-18 13:48:42 +07:00
datadunia 359a23c079 chore: update dashboard-ui + device-agent submodule refs 2026-06-18 12:45:18 +07:00
datadunia facd8bd7d5 chore: update all submodule refs (single instance, Windows IP, Docker debug) 2026-06-18 12:24:15 +07:00
datadunia 83722abd32 chore: update server-core submodule ref 2026-06-18 11:24:32 +07:00
datadunia 359bd6f9c4 chore: update device-agent + server-core submodule refs (Windows support, DeviceID) 2026-06-18 11:15:22 +07:00
datadunia e81c1dd448 chore(gitignore): exclude test directories (.tests/, tests/) 2026-06-18 11:14:51 +07:00
8 changed files with 142 additions and 23 deletions
+2 -1
View File
@@ -23,6 +23,7 @@ jobs:
with:
go-version: '1.26'
cache: true
cache-dependency-path: apps/device-agent/go.sum
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-src
@@ -59,7 +60,7 @@ jobs:
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
OUT="bin/nexus-device-agent-${GOOS}-${GOARCH}${EXT}"
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
echo "=== Build output ==="
ls -la bin/
+1
View File
@@ -23,6 +23,7 @@ jobs:
with:
go-version: '1.26'
cache: true
cache-dependency-path: apps/server-core/go.sum
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-src
+2
View File
@@ -29,6 +29,7 @@ jobs:
with:
go-version: '1.26'
cache: true
cache-dependency-path: apps/server-core/go.sum
- name: Test (skip nftables - needs root)
working-directory: apps/server-core
run: go test $(go list ./... | grep -v internal/firewall) -tags dev -cover -count=1
@@ -49,6 +50,7 @@ jobs:
with:
go-version: '1.26'
cache: true
cache-dependency-path: apps/device-agent/go.sum
- name: Test
working-directory: apps/device-agent
run: go test ./... -cover
+2
View File
@@ -36,6 +36,8 @@ temp_*
# Build output
dist/
/dist/
.tests/
tests/
# OpenCode
connect_remote.txt
+121 -8
View File
@@ -1,11 +1,22 @@
# 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
```
@@ -15,12 +26,12 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin
│ ├── 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)
├── 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
├── 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)
@@ -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
@@ -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 "..."
```
## 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
- 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`
- 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)