Compare commits

..

52 Commits

Author SHA1 Message Date
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
datadunia 9433dc3547 fix: device-agent v1.0.0-dev.33 - Windows syscall fixes
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 1m54s
CI / build-device-agent (push) Successful in 9m21s
CI / build-dashboard (push) Successful in 3m15s
CI / build-docs (push) Has been skipped
CI / release (push) Successful in 29s
2026-06-18 10:16:10 +07:00
6 changed files with 138 additions and 23 deletions
+1 -1
View File
@@ -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/
+2
View File
@@ -36,6 +36,8 @@ temp_*
# Build output # Build output
dist/ dist/
/dist/ /dist/
.tests/
tests/
# OpenCode # OpenCode
connect_remote.txt connect_remote.txt
+132 -19
View File
@@ -1,29 +1,40 @@
# 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
``` ```
./ ./
├── apps/ ├── apps/
│ ├── server-core/ # Go/Gin API backend (submodule) │ ├── server-core/ # Go/Gin API backend (submodule)
│ ├── 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)
``` ```
**CRITICAL**: `apps/*` are **git submodules** — clone with `--recurse-submodules`. **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 | | 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)