Compare commits

...

95 Commits

Author SHA1 Message Date
datadunia 88fa60901e chore: remove stale device-agent-embedded submodule from index 2026-07-13 13:40:03 +07:00
datadunia f95763ae42 chore: update device-agent submodule + docker-compose + portfolio docs 2026-07-13 07:52:29 +07:00
datadunia 45d2e51062 chore: update all submodule refs 2026-07-11 18:48:37 +07:00
datadunia 7c9110ce9c 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 / android-agent-test (push) Has been skipped
CI / build-server-core (push) Failing after 5m4s
CI / build-device-agent (push) Failing after 24s
CI / build-dashboard (push) Failing after 31s
CI / build-android-agent (push) Failing after 2m53s
CI / build-docs (push) Has been skipped
CI / release (push) Has been skipped
CI / deploy-prod (push) Has been skipped
CI / deploy-dev (push) Has been skipped
- Fix: Disconnect button enabled during connecting (cancel on no internet)
- Add: Port Forwards display in tray menu
2026-07-10 03:10:47 +07:00
datadunia 115019846e chore: update submodules to latest commits
- device-agent: dynamic gRPC port from server config
- server-core: dynamic gRPC port from server config
- android-agent: dynamic gRPC port + build scripts
2026-07-09 21:21:58 +07:00
datadunia d7d471308a chore: update device-agent submodule (unused import fix) 2026-07-09 00:42:03 +07:00
datadunia 8654505933 chore: update device-agent submodule (config_hash + autostart) 2026-07-09 00:37:02 +07:00
datadunia 482cabce93 chore: update server-core submodule (heartbeat config_hash) 2026-07-09 00:28:50 +07:00
datadunia 13a7bfef1e chore: update all submodule refs 2026-07-08 15:04:58 +07:00
datadunia 326e0774a8 security: remove unprotected device forwards endpoint, fix traffic/report auth
- server-core: removed ListByDeviceID (no auth), moved traffic/report to protected group
- device-agent: removed redundant GetForwards HTTP calls (forwards delivered via gRPC/heartbeat)
2026-07-08 12:06:11 +07:00
datadunia d61c470b97 chore: update device-agent submodule (export/import .conf format) 2026-07-08 05:24:28 +07:00
datadunia 52fc7c749c chore: update device-agent submodule ref
Submodule commits: fix(device-agent): 4 bug fixes + import/export + terminal status
- Fix #1: UDP proxy NAT session table
- Fix #2: CLI config file loading
- Fix #3: Remove unused LoadForwards param
- Fix #4: tunnel status/export/import + Windows tray import/export
2026-07-07 22:33:11 +07:00
datadunia 5d60c7d160 chore: update android-agent submodule 2026-07-07 20:41:07 +07:00
datadunia 8cadfc4657 docs: update AGENTS.md 2026-07-07 17:22:28 +07:00
datadunia 5d3761c56c chore: update android-agent + server-core submodules 2026-07-07 17:16:23 +07:00
datadunia b04d47a1bf chore: update android-agent submodule 2026-07-07 09:34:25 +07:00
datadunia 59a8436a45 chore: update all submodule refs + CI/CD workflows + AGENTS.md 2026-07-07 09:28:06 +07:00
datadunia 2fb3109b9f chore: update server-core submodule - PushSuspend/PushResume error logging 2026-07-06 21:12:23 +07:00
datadunia 84689b208e docs: add ESP32 and iOS agent architecture reference
- reference/esp32-agent/: DESIGN, HARDWARE, API_COMPAT, ARCHITECTURE, README

- reference/ios-agent/: DESIGN, API_COMPAT, UI_DESIGN, ARCHITECTURE, README

- Both mirror Android agent architecture, UI design, and heartbeat flow

- Architecture-only (no code) — avoids submodule conflicts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-07-06 14:20:10 +07:00
datadunia 4af46dadad chore: remove device-agent-embedded submodule + update android-agent ref + README
- .gitmodules: remove device-agent-embedded entry (moved to reference/esp32-agent/)

- README.md: add Android Agent and ESP32 Agent to component list, update file structure

- apps/android-agent: update submodule ref (gRPC lifecycle + UI overhaul)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-07-06 14:18:11 +07:00
datadunia 5ed8e54f16 chore: update submodules — server-core hash fix + android-agent sticky UI + esp32 agent 2026-07-04 15:52:38 +07:00
datadunia 1124cd8159 docs: update transport fallback chain with smart reconnection 2026-07-04 13:10:40 +07:00
datadunia c204f75978 chore: update android-agent + server-core submodules
- android-agent: resolveAllowedIps safety net, null-config handling, file fallback
- server-core: centralized Device.ResolveAllowedIPs()
2026-07-04 12:19:19 +07:00
datadunia 8719535f55 chore: update android-agent submodule (gRPC fallback + port forwarding) 2026-07-04 09:47:57 +07:00
datadunia dd3aaafffb gitignore 2026-07-04 08:34:46 +07:00
datadunia 934dc291f1 chore: update android-agent submodule (gRPC fix — align with device-agent) 2026-07-03 17:37:23 +07:00
datadunia be5178e04f docs: add android-build.md with build/deploy commands 2026-07-03 17:11:45 +07:00
datadunia 6cb4e53598 chore: update android-agent submodule (gRPC signaling + HWID fix) 2026-07-03 16:37:47 +07:00
datadunia 68203f2eb3 chore: update android-agent submodule pointer, add Android build artifacts to .gitignore 2026-07-03 14:50:13 +07:00
datadunia 24e83ee8cd chore: update submodule refs 2026-07-03 02:05:01 +07:00
datadunia 7cce8c39a6 chore: update dashboard-ui + server-core submodules 2026-07-03 01:34:18 +07:00
datadunia ca4107e2bc fix: disable Redis RDB persistence to resolve overcommit_memory warning
Redis in LXC/Proxmox cannot change kernel vm.overcommit_memory.
Disabling RDB saves eliminates the warning with no functional impact
(Redis used only for heartbeat TTL cache, not persistent storage).
2026-06-26 22:21:37 +07:00
datadunia 192b753f5d chore: update server-core submodule 2026-06-26 17:50:57 +07:00
datadunia 4eea268ec5 refactor: merge setup.sh into update.sh for seamless first-run
- update.sh now auto-generates .env from .env.example if missing
- Generates random JWT/SALT keys on first run
- Existing .env is never overwritten
- Deleted redundant setup.sh
- Updated all documentation references
2026-06-26 17:23:52 +07:00
datadunia fcb1de2cac feat(installer): add WireGuard check + auto-install for native install 2026-06-26 11:16:13 +07:00
datadunia 4df2e0aeeb fix: update all submodules recursively, but only detect Docker changes
- Use --recursive to update all submodules (including device-agent)
- Hash only server-core + dashboard-ui for rebuild detection
- device-agent and device-agent-embedded don't trigger Docker rebuild
2026-06-26 10:39:20 +07:00
datadunia 2426a7b42c fix: add --recursive flag for nested submodule updates 2026-06-26 10:33:23 +07:00
datadunia d6610f82e9 fix: use explicit submodule paths instead of --exclude flag
git submodule update does not support --exclude flag.
Explicitly specify Docker submodules: server-core, dashboard-ui
2026-06-26 10:28:22 +07:00
datadunia 80ba316d4a feat: add embedded agent submodule and update architecture
- Add device-agent-embedded submodule (ESP32 WireGuard agent)
- Update README.md with embedded agent architecture
- Update update.sh to exclude device-agent and device-agent-embedded
  (both built separately, not part of Docker deployment)
- Add docs/DESIGN.md, API_COMPAT.md, HARDWARE.md for embedded agent
- Add Kconfig for menuconfig options
- Add sdkconfig.defaults for ESP-IDF configuration
2026-06-26 10:03:20 +07:00
datadunia 7a43ec8f2d chore: update device-agent submodule + deployment docs 2026-06-23 10:43:16 +07:00
datadunia 05d496432a chore: update device-agent submodule — Windows service mode, tray integration, crash fixes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-23 07:11:20 +07:00
datadunia d6c53f6f86 docs: add NexusGuard portfolio (architecture, tech-stack, features, deployment) 2026-06-21 14:25:33 +07:00
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
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
36 changed files with 5693 additions and 102 deletions
+130
View File
@@ -0,0 +1,130 @@
name: Build Android Agent
on:
workflow_call:
inputs:
build_type:
description: 'Build type (debug or release)'
required: false
type: string
default: 'release'
jobs:
build:
runs-on: ubuntu-latest
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Configure git auth for submodules
run: git config --global url."https://x-access-token:${{ secrets.BUILD_TOKEN }}@git.datadunia.com/".insteadOf "https://git.datadunia.com/"
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
token: ${{ secrets.BUILD_TOKEN }}
persist-credentials: true
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Check for signing key
id: check-signing
run: |
if [ -n "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" ]; then
echo "has_key=true" >> "$GITHUB_OUTPUT"
echo "Signing: Using production keystore from secrets"
else
echo "has_key=false" >> "$GITHUB_OUTPUT"
echo "Signing: No keystore found, using debug/self-signed"
fi
- name: Decode keystore
if: steps.check-signing.outputs.has_key == 'true'
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > /tmp/release.keystore
- name: Create keystore properties
if: steps.check-signing.outputs.has_key == 'true'
run: |
cat > /tmp/keystore.properties << EOF
storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
storeFile=/tmp/release.keystore
EOF
- name: Configure signing (production)
if: steps.check-signing.outputs.has_key == 'true'
working-directory: apps/android-agent
run: |
# Inject signing config into build.gradle.kts
cat >> app/build.gradle.kts << 'GRADLE_EOF'
android {
signingConfigs {
create("release") {
storeFile = file("/tmp/release.keystore")
storePassword = System.getenv("KEYSTORE_PASSWORD") ?: ""
keyAlias = System.getenv("KEY_ALIAS") ?: ""
keyPassword = System.getenv("KEY_PASSWORD") ?: ""
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}
GRADLE_EOF
env:
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Grant execute permission for gradlew
working-directory: apps/android-agent
run: chmod +x gradlew
- name: Build debug APK (no signing key)
if: steps.check-signing.outputs.has_key == 'false'
working-directory: apps/android-agent
run: ./gradlew assembleDebug
- name: Build release APK (with signing key)
if: steps.check-signing.outputs.has_key == 'true'
working-directory: apps/android-agent
run: ./gradlew assembleRelease
- name: Upload debug APK
if: steps.check-signing.outputs.has_key == 'false'
uses: actions/upload-artifact@v3
with:
name: nexusguard-android-debug
path: apps/android-agent/app/build/outputs/apk/debug/app-debug.apk
- name: Upload release APK
if: steps.check-signing.outputs.has_key == 'true'
uses: actions/upload-artifact@v3
with:
name: nexusguard-android-release
path: apps/android-agent/app/build/outputs/apk/release/app-release.apk
- name: Upload mapping file (release only)
if: steps.check-signing.outputs.has_key == 'true'
uses: actions/upload-artifact@v3
with:
name: nexusguard-android-mapping
path: apps/android-agent/app/build/outputs/mapping/release/mapping.txt
if-no-files-found: warn
- name: Cleanup keystore
if: always()
run: rm -f /tmp/release.keystore /tmp/keystore.properties
+2 -1
View File
@@ -23,6 +23,7 @@ jobs:
with: with:
go-version: '1.26' go-version: '1.26'
cache: true cache: true
cache-dependency-path: apps/device-agent/go.sum
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1 - uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-src id: hash-src
@@ -59,7 +60,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/
+1
View File
@@ -23,6 +23,7 @@ jobs:
with: with:
go-version: '1.26' go-version: '1.26'
cache: true cache: true
cache-dependency-path: apps/server-core/go.sum
- uses: https://gitea.com/actions/go-hashfiles@v0.0.1 - uses: https://gitea.com/actions/go-hashfiles@v0.0.1
id: hash-src id: hash-src
+70 -8
View File
@@ -8,13 +8,17 @@ on:
- 'v*-beta*' - 'v*-beta*'
- 'v*-test*' - 'v*-test*'
- 'v[0-9]*.[0-9]*.[0-9]' - 'v[0-9]*.[0-9]*.[0-9]'
pull_request:
branches:
- main
- develop
jobs: jobs:
# ==================================================================== # ====================================================================
# TESTS — test tags only # TESTS — test tags only
# ==================================================================== # ====================================================================
server-core-test: server-core-test:
if: contains(gitea.ref_name, 'test') if: contains(gitea.ref_name, 'test') || github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -29,12 +33,13 @@ jobs:
with: with:
go-version: '1.26' go-version: '1.26'
cache: true cache: true
cache-dependency-path: apps/server-core/go.sum
- name: Test (skip nftables - needs root) - name: Test (skip nftables - needs root)
working-directory: apps/server-core working-directory: apps/server-core
run: go test $(go list ./... | grep -v internal/firewall) -tags dev -cover -count=1 run: go test $(go list ./... | grep -v internal/firewall) -tags dev -cover -count=1
device-agent-test: device-agent-test:
if: contains(gitea.ref_name, 'test') if: contains(gitea.ref_name, 'test') || github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -49,12 +54,13 @@ jobs:
with: with:
go-version: '1.26' go-version: '1.26'
cache: true cache: true
cache-dependency-path: apps/device-agent/go.sum
- name: Test - name: Test
working-directory: apps/device-agent working-directory: apps/device-agent
run: go test ./... -cover run: go test ./... -cover
dashboard-test: dashboard-test:
if: contains(gitea.ref_name, 'test') if: contains(gitea.ref_name, 'test') || github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -75,36 +81,70 @@ jobs:
working-directory: apps/dashboard-ui working-directory: apps/dashboard-ui
run: npm run build run: npm run build
android-agent-test:
if: contains(gitea.ref_name, 'test') || github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
token: ${{ secrets.BUILD_TOKEN }}
persist-credentials: true
- name: Configure git auth for submodules
run: git config --global url."https://x-access-token:${{ secrets.BUILD_TOKEN }}@git.datadunia.com/".insteadOf "https://git.datadunia.com/"
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Grant execute permission for gradlew
working-directory: apps/android-agent
run: chmod +x gradlew
- name: Build debug APK
working-directory: apps/android-agent
run: ./gradlew assembleDebug
# ==================================================================== # ====================================================================
# BUILD — after tests pass (release) or directly (dev/beta) # BUILD — after tests pass (release) or directly (dev/beta)
# ==================================================================== # ====================================================================
build-server-core: build-server-core:
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled() if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
needs: [server-core-test, device-agent-test, dashboard-test] needs: [server-core-test, device-agent-test, dashboard-test, android-agent-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: build-device-agent:
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled() if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
needs: [server-core-test, device-agent-test, dashboard-test] needs: [server-core-test, device-agent-test, dashboard-test, android-agent-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: build-dashboard:
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled() if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
needs: [server-core-test, device-agent-test, dashboard-test] needs: [server-core-test, device-agent-test, dashboard-test, android-agent-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
build-android-agent:
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
needs: [server-core-test, device-agent-test, dashboard-test, android-agent-test]
runs-on: ubuntu-latest
uses: ./.gitea/workflows/build_android_agent.yaml
secrets: inherit
# ==================================================================== # ====================================================================
# DOCS — after builds pass, release tags only # DOCS — after builds pass, release tags only
# ==================================================================== # ====================================================================
build-docs: build-docs:
if: always() && !contains(gitea.ref_name, 'dev') && !contains(gitea.ref_name, 'beta') && !contains(gitea.ref_name, 'test') && !failure() && !cancelled() 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] needs: [build-server-core, build-device-agent, build-dashboard, build-android-agent]
runs-on: ubuntu-latest runs-on: ubuntu-latest
uses: ./.gitea/workflows/docs_call.yaml uses: ./.gitea/workflows/docs_call.yaml
secrets: inherit secrets: inherit
@@ -114,9 +154,31 @@ jobs:
# ==================================================================== # ====================================================================
release: release:
if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled() if: always() && !contains(gitea.ref_name, 'test') && !failure() && !cancelled()
needs: [build-server-core, build-device-agent, build-dashboard, build-docs] needs: [build-server-core, build-device-agent, build-dashboard, build-android-agent, 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
# ====================================================================
# DEPLOY DEV — auto-deploy on dev tags
# ====================================================================
deploy-dev:
if: contains(gitea.ref_name, 'dev') && !failure() && !cancelled()
needs: [build-server-core, build-device-agent, build-dashboard, build-android-agent]
uses: ./.gitea/workflows/deploy_call.yaml
with:
environment: dev
secrets: inherit
# ====================================================================
# DEPLOY PROD — manual trigger after release
# ====================================================================
deploy-prod:
if: startsWith(gitea.ref_name, 'v') && !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, build-android-agent, build-docs]
uses: ./.gitea/workflows/deploy_call.yaml
with:
environment: production
secrets: inherit
+89
View File
@@ -0,0 +1,89 @@
name: Deploy
on:
workflow_call:
inputs:
environment:
description: 'Target environment (dev or production)'
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Configure git auth for submodules
run: git config --global url."https://x-access-token:${{ secrets.BUILD_TOKEN }}@git.datadunia.com/".insteadOf "https://git.datadunia.com/"
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.BUILD_TOKEN }}
persist-credentials: true
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: ./artifacts
- name: Deploy to ${{ inputs.environment }}
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
echo "Deploying to ${{ inputs.environment }}..."
echo "Host: $DEPLOY_HOST"
echo "Path: $DEPLOY_PATH"
# Setup SSH
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
# Upload artifacts
echo "Uploading server-core..."
scp -i ~/.ssh/deploy_key artifacts/server-core-linux-amd64/server-core \
"${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/bin/"
echo "Uploading device-agent..."
for platform in linux-amd64 linux-arm64; do
scp -i ~/.ssh/deploy_key "artifacts/nexus-device-agent-${platform}/nexus-device-agent-${platform}" \
"${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/bin/"
done
echo "Uploading dashboard..."
if [ -d "artifacts/dashboard-ui-dist/dist" ]; then
tar -czf /tmp/dashboard-ui.tar.gz -C artifacts/dashboard-ui-dist dist/
scp -i ~/.ssh/deploy_key /tmp/dashboard-ui.tar.gz \
"${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"
fi
# Deploy on server
echo "Running deployment..."
ssh -i ~/.ssh/deploy_key "${DEPLOY_USER}@${DEPLOY_HOST}" << 'DEPLOY_SCRIPT'
cd "${DEPLOY_PATH}" || exit 1
# Stop services
echo "Stopping services..."
docker compose down || true
# Run update script
echo "Running update..."
bash update.sh --force
# Verify services
echo "Verifying services..."
docker compose ps
echo "Deployment complete!"
DEPLOY_SCRIPT
echo "Deployed to ${{ inputs.environment }} successfully!"
- name: Cleanup
if: always()
run: rm -rf ~/.ssh/deploy_key /tmp/dashboard-ui.tar.gz
+9 -1
View File
@@ -49,7 +49,8 @@ jobs:
"device-agent-linux-amd64": "${SERVER}/${REPO}/releases/download/${VERSION}/nexus-device-agent-linux-amd64", "device-agent-linux-amd64": "${SERVER}/${REPO}/releases/download/${VERSION}/nexus-device-agent-linux-amd64",
"device-agent-linux-arm64": "${SERVER}/${REPO}/releases/download/${VERSION}/nexus-device-agent-linux-arm64", "device-agent-linux-arm64": "${SERVER}/${REPO}/releases/download/${VERSION}/nexus-device-agent-linux-arm64",
"device-agent-windows-amd64": "${SERVER}/${REPO}/releases/download/${VERSION}/nexus-device-agent-windows-amd64.exe", "device-agent-windows-amd64": "${SERVER}/${REPO}/releases/download/${VERSION}/nexus-device-agent-windows-amd64.exe",
"dashboard-ui": "${SERVER}/${REPO}/releases/download/${VERSION}/dashboard-ui-dist.tar.gz" "dashboard-ui": "${SERVER}/${REPO}/releases/download/${VERSION}/dashboard-ui-dist.tar.gz",
"android-agent": "${SERVER}/${REPO}/releases/download/${VERSION}/nexusguard-android.apk"
} }
} }
ENDJSON ENDJSON
@@ -115,6 +116,13 @@ jobs:
upload_asset "dashboard-ui-dist.tar.gz" "dashboard-ui-dist.tar.gz" upload_asset "dashboard-ui-dist.tar.gz" "dashboard-ui-dist.tar.gz"
fi fi
# Android Agent APK
if [ -f "artifacts/nexusguard-android-debug/nexusguard-android-debug.apk" ]; then
upload_asset "artifacts/nexusguard-android-debug/nexusguard-android-debug.apk" "nexusguard-android.apk"
elif [ -f "artifacts/nexusguard-android-release/nexusguard-android-release.apk" ]; then
upload_asset "artifacts/nexusguard-android-release/nexusguard-android-release.apk" "nexusguard-android.apk"
fi
# Docs tar.gz # Docs tar.gz
if [ -d "artifacts/docs-dist" ]; then if [ -d "artifacts/docs-dist" ]; then
cd artifacts/docs-dist cd artifacts/docs-dist
+14
View File
@@ -36,6 +36,20 @@ temp_*
# Build output # Build output
dist/ dist/
/dist/ /dist/
tests/
.tests/
# Android build artifacts
*.aar
*.apk
*.aab
android-agent/app/build/
android-agent/build/
android-agent/.gradle/
android-agent/local.properties
# Go mobile build output
nexusguard-agent.aar
# OpenCode # OpenCode
connect_remote.txt connect_remote.txt
+3
View File
@@ -7,3 +7,6 @@
[submodule "apps/device-agent"] [submodule "apps/device-agent"]
path = apps/device-agent path = apps/device-agent
url = https://git.datadunia.com/nexusguard/nexus-device-agent.git url = https://git.datadunia.com/nexusguard/nexus-device-agent.git
[submodule "apps/android-agent"]
path = apps/android-agent
url = https://git.datadunia.com/nexusguard/nexus-android-agent.git
+96
View File
@@ -0,0 +1,96 @@
# Android Agent Build & Deploy
## Prerequisites
- Android SDK installed (compileSdk 34, minSdk 26)
- Device connected via USB with USB debugging enabled
- `adb devices` shows device
## Device
```
9b2adc22 device
```
## Build Commands
### Build Debug APK
```powershell
cd D:\www-project\NexusGuard\apps\android-agent
.\gradlew assembleDebug
```
Output: `app/build/outputs/apk/debug/app-debug.apk`
### Install on Device
```powershell
adb install -r app\build\outputs\apk\debug\app-debug.apk
```
### Launch App
```powershell
adb shell am start -n com.datadunia.nexusguard/.MainActivity
```
### View Logs
```powershell
adb logcat -s AgentService GrpcClient Heartbeat TunnelManager Provisioning LogBuffer
```
### Check Device Connection
```powershell
adb devices
```
## One-Shot Build + Install + Launch
```powershell
cd D:\www-project\NexusGuard\apps\android-agent
.\gradlew assembleDebug && adb install -r app\build\outputs\apk\debug\app-debug.apk && adb shell am start -n com.datadunia.nexusguard/.MainActivity
```
## Architecture (matches device-agent)
- **gRPC signaling**: GrpcClient.kt — bidirectional streaming with fallback (HTTPS domain → WG IP → plaintext)
- **HTTP heartbeat**: Heartbeat.kt — POST /api/v1/heartbeat every 30s (fallback when gRPC down)
- **Background service**: AgentService.kt — foreground service with notification (START_STICKY)
- **Tunnel**: TunnelManager.kt — WireGuard via VPNService + GoBackend
- **HWID**: SHA-256(ANDROID_ID + manufacturer + model + device), persisted in EncryptedSharedPreferences
- **Provisioning**: Provisioning.kt — HTTP POST /api/v1/provisioning
- **Auto-start**: BootReceiver.kt — starts agent on device boot
## Commands (Agent)
- gRPC: HelloMessage → ConfigUpdate (full tunnel config + port forwards)
- gRPC: HeartbeatRequest every 30s → HeartbeatResponse (config_hash + forwards_changed)
- gRPC: Suspend/Resume/Reconnect/Disconnect from server
- HTTP heartbeat: fallback when gRPC disconnected
## .gitignore (Android artifacts)
```
*.aar
*.apk
*.aab
android-agent/app/build/
android-agent/build/
android-agent/.gradle/
android-agent/local.properties
```
## Commit Workflow
```powershell
# 1. Build
cd D:\www-project\NexusGuard\apps\android-agent
.\gradlew assembleDebug
# 2. Install + test
adb install -r app\build\outputs\apk\debug\app-debug.apk
adb shell am start -n com.datadunia.nexusguard/.MainActivity
# 3. Commit submodule
cd D:\www-project\NexusGuard\apps\android-agent
git add -A
git commit -m "feat: message"
git push origin main
# 4. Commit parent repo
cd D:\www-project\NexusGuard
git add apps/android-agent
git commit -m "chore: update android-agent submodule"
git push origin main
```
+152 -10
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
``` ```
@@ -17,10 +28,9 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin
├── 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 ├── update.sh # Docker update: auto-generate .env + 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)
@@ -28,6 +38,19 @@ NexusGuard SD-WAN Suite — Enterprise Zero-Trust SD-WAN with WireGuard tunnelin
**CRITICAL**: `apps/*` are **git submodules** — clone with `--recurse-submodules`. **CRITICAL**: `apps/*` are **git submodules** — clone with `--recurse-submodules`.
## SUBMODULE KNOWLEDGE BASES
Each submodule has its own `AGENTS.md` with detailed architecture, conventions, and anti-patterns:
| Submodule | AGENTS.md | Scope |
|-----------|-----------|-------|
| Server Core | `apps/server-core/AGENTS.md` | API handlers, database models, firewall rules, gRPC signaling, WireGuard management |
| Dashboard UI | `apps/dashboard-ui/AGENTS.md` | Vue 3 components, Pinia stores, TailwindCSS styling, API client |
| Device Agent | `apps/device-agent/AGENTS.md` | Go daemon, system tray, memory-injected WireGuard, heartbeat, gRPC signaling |
| Android Agent | `apps/android-agent/AGENTS.md` | Kotlin VPNService, GoBackend tunnel, HTTP heartbeat, port forwarding, boot auto-start |
**Rule**: When working on a submodule, ALWAYS read its `AGENTS.md` first for project-specific conventions.
## WHERE TO LOOK ## WHERE TO LOOK
| Task | Location | Notes | | Task | Location | Notes |
|------|----------|-------| |------|----------|-------|
@@ -35,16 +58,85 @@ 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) → `api-nexus.datadunia.com:443`
2. gRPC via WireGuard IP (insecure, tunnel-encrypted) → `10.172.21.1:8080`
3. HTTP heartbeat (fallback) → `serverURL/api/v1/heartbeat`
**Smart reconnection:** If gRPC was never connected (HTTPS blocked by Cloudflare), agent skips HTTPS on reconnect and only tries WG IP. If gRPC was connected before, agent tries last successful transport first.
### 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 = REAL-TIME Channel (when connected)
Handles with **zero delay**:
- **ConfigUpdate** → immediate tunnel rebuild (no heartbeat wait)
- Suspend / Resume → immediate tunnel stop/rebuild
- Reconnect / Disconnect → immediate action
- StatusReport from agent (tunnel_up, lastHandshake, state)
- Ping/Pong keepalive (20s)
When gRPC disconnects → falls back to HTTP heartbeat (30s delay for config sync).
### 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 gRPC ConfigUpdate → immediate tunnel rebuild (no delay)
6. On heartbeat config change → rebuild tunnel (30s delay, HTTP fallback)
7. On heartbeat stale handshake → rebuild tunnel
8. On heartbeat failure+recovery → rebuild tunnel
9. On gRPC suspend → send heartbeat(tunnel_up=true) → stop tunnel (DisconnectReason.SUSPENDED) → heartbeat continues
10. On gRPC resume → rebuild tunnel from server config → send heartbeat(tunnel_up=true)
### 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 +152,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,11 +178,12 @@ 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
- **NEVER** commit build artifacts (binaries, `dist/`) - **NEVER** commit build artifacts (binaries, `dist/`, APK, `.apk`, `.aab`)
- **NEVER** leave temp/debug/test outputs in project root — all must go in `.tests/` folder
- **NEVER** force push - **NEVER** force push
- **NEVER** create cross-phase workarounds - **NEVER** create cross-phase workarounds
@@ -119,6 +219,15 @@ if d.AllowInternet {
// Do NOT override with EndpointAllowedIPs here // Do NOT override with EndpointAllowedIPs here
``` ```
### Client-side AllowedIPs resolution (`Device.ResolveAllowedIPs()`)
Centralized in `internal/models/models.go`. Used by `buildConfigUpdate()`, heartbeat, and provisioning.
Priority chain:
1. `AllowInternet``0.0.0.0/0`
2. `EndpointAllowedIPs` (if set) → use as-is
3. Fallback → compose `IPPoolCIDR, serverWGIP/32` (e.g. `10.172.21.0/24,10.172.21.1/32`)
**Never** use `InternalIP/32` as client-side AllowedIPs — that value is server-side WireGuard kernel routing only.
### Database field: `endpoint_allowed_ips` ### Database field: `endpoint_allowed_ips`
- Used for **client config** and **firewall rules** - Used for **client config** and **firewall rules**
- NOT used for server-side WireGuard kernel config - NOT used for server-side WireGuard kernel config
@@ -200,10 +309,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)
+14 -9
View File
@@ -3,10 +3,12 @@
NexusGuard is an Enterprise Zero-Trust SD-WAN solution built with Go, Vue 3, and WireGuard. It enables stealth VPN tunneling, centralized IPAM, and real-time network isolation via `nftables`. NexusGuard is an Enterprise Zero-Trust SD-WAN solution built with Go, Vue 3, and WireGuard. It enables stealth VPN tunneling, centralized IPAM, and real-time network isolation via `nftables`.
## 🏗️ System Architecture ## 🏗️ System Architecture
This suite contains three main components: This suite contains four main components:
1. **[Server Core](http://git.datadunia.com/nexusguard/nexus-server-core)**: The central API and VPN Hub managing database state, token distribution, and Linux firewall isolation. 1. **[Server Core](http://git.datadunia.com/nexusguard/nexus-server-core)**: The central API and VPN Hub managing database state, token distribution, and Linux firewall isolation.
2. **[Dashboard UI](http://git.datadunia.com/nexusguard/nexus-dashboard-ui)**: The Admin Web GUI for managing Nodes, Users, Devices, and Firewall rules. Features a futuristic glassmorphism design system using Vue 3, Vite, TailwindCSS v4, and HeadlessUI. 2. **[Dashboard UI](http://git.datadunia.com/nexusguard/nexus-dashboard-ui)**: The Admin Web GUI for managing Nodes, Users, Devices, and Firewall rules. Features a futuristic glassmorphism design system using Vue 3, Vite, TailwindCSS v4, and HeadlessUI.
3. **[Device Agent](http://git.datadunia.com/nexusguard/nexus-device-agent)**: A stealth background daemon for Linux client machines that establishes memory-injected WireGuard tunnels. 3. **[Device Agent](http://git.datadunia.com/nexusguard/nexus-device-agent)**: A stealth background daemon for Linux/Windows/macOS client machines that establishes memory-injected WireGuard tunnels.
4. **[Android Agent](http://git.datadunia.com/nexusguard/nexus-android-agent)**: Android-based WireGuard client for mobile devices using VPNService.
5. **ESP32 Agent**: Architecture reference only (see `reference/esp32-agent/`).
--- ---
@@ -18,16 +20,17 @@ Clone this repository and start all services:
```bash ```bash
git clone https://git.datadunia.com/nexusguard/Nexus-Guard-Suite.git git clone https://git.datadunia.com/nexusguard/Nexus-Guard-Suite.git
cd Nexus-Guard-Suite cd Nexus-Guard-Suite
./setup.sh # Generate .env file automatically bash update.sh # Auto-generates .env + pulls, builds, migrates, starts
# Edit .env with your configuration
bash update.sh # Pull, build, migrate, and start
``` ```
The first run will automatically: The first run will automatically:
- Pull the latest code and build the Docker containers (via `update.sh`). - Generate `.env` from `.env.example` with auto-generated JWT/SALT keys.
- Pull the latest code and build the Docker containers.
- Auto-generate the **Local Primary Node** WireGuard keys on the first boot. - Auto-generate the **Local Primary Node** WireGuard keys on the first boot.
- Create the database schema via automated migration. - Create the database schema via automated migration.
Edit `.env` afterward to customize `DB_PASSWORD`, `API_PORT`, `VITE_API_BASE_URL`.
**Update Options:** **Update Options:**
```bash ```bash
bash update.sh # Smart update (only rebuild if changes detected) bash update.sh # Smart update (only rebuild if changes detected)
@@ -263,12 +266,14 @@ NexusGuard/
├── apps/ ├── apps/
│ ├── server-core/ # Go/Gin API backend │ ├── server-core/ # Go/Gin API backend
│ ├── dashboard-ui/ # Vue 3 frontend │ ├── dashboard-ui/ # Vue 3 frontend
── device-agent/ # Go client agent ── device-agent/ # Go client agent
│ └── android-agent/ # Kotlin Android agent
├── reference/
│ └── esp32-agent/ # ESP32 architecture docs (not code)
├── bin/ # Built binaries (gitignored) ├── bin/ # Built binaries (gitignored)
├── docker-compose.yml # Docker orchestration ├── docker-compose.yml # Docker orchestration
├── docker-compose.dev.yml # Docker dev (hot-reload) ├── docker-compose.dev.yml # Docker dev (hot-reload)
├── update.sh # Docker update script ├── update.sh # Docker update + auto-setup script
├── setup.sh # Docker initial setup
├── nexusguard-install.sh # Native install script ├── nexusguard-install.sh # Native install script
├── nexusguard-uninstall.sh # Native uninstall script ├── nexusguard-uninstall.sh # Native uninstall script
├── .env.example # Environment template ├── .env.example # Environment template
Submodule apps/android-agent added at ce70c3e197
+21
View File
@@ -15,11 +15,17 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
restart: unless-stopped restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks: networks:
- nexusnet - nexusnet
redis: redis:
image: redis:7-alpine image: redis:7-alpine
command: redis-server --save "" --appendonly no
volumes: volumes:
- redisdata:/data - redisdata:/data
ports: ports:
@@ -30,6 +36,11 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
restart: unless-stopped restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks: networks:
- nexusnet - nexusnet
@@ -52,6 +63,11 @@ services:
- NET_ADMIN - NET_ADMIN
- NET_RAW - NET_RAW
restart: unless-stopped restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
dashboard-ui: dashboard-ui:
build: build:
@@ -66,6 +82,11 @@ services:
depends_on: depends_on:
- server-core - server-core
restart: unless-stopped restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
networks: networks:
- nexusnet - nexusnet
extra_hosts: extra_hosts:
+85
View File
@@ -0,0 +1,85 @@
# Android Agent UI Redesign
**Date:** 2026-07-04
**Scope:** NexusGuard Android Agent main activity UI
## Summary
Complete redesign of the Android agent main activity UI with a dark theme, green accent color scheme, copy-tap support, and new port forwarding display.
## Files Changed
| File | Change |
|------|--------|
| `app/src/main/res/values/strings.xml` | Added 13 new string resources for UI labels |
| `app/src/main/res/values/colors.xml` | Added 8 new color definitions (dark/green theme) |
| `app/src/main/res/values/themes.xml` | Applied custom Material3 theme attributes |
| `app/src/main/res/layout/activity_main.xml` | Complete layout redesign |
| `app/src/main/java/com/datadunia/nexusguard/MainActivity.kt` | Updated Kotlin bindings and logic |
## Design Changes
### Theme
- Dark background (`#0D1117`) with green accent (`#00C853`)
- Material3 `DayNight.NoActionBar` parent theme with custom color overrides
- Card borders use `#21262D` stroke color
### Header
- App name "NexusGuard" displayed in green accent color
- MaterialSwitch for VPN toggle with ON/OFF labels
- Status text below switch (colored by state)
### Status Device Agent Card
- Renamed from "Informasi Tunnel" to "Status Device Agent"
- Six data rows: IP Address, Admin Web, Device ID, Handshake Terakhir, Allowed IPs, Transport
- All value TextViews are selectable (`textIsSelectable="true"`) and copy-tappable
- Device ID displays full text without truncation (no `ellipsize` or `singleLine`)
- Admin Web shows fixed domain `https://api-nexus.datadunia.com`
### Port Forwarding Card (New)
- Displays active port forwards from `PortForwarder.getActiveForwards()`
- Shows protocol badge (TCP/UDP) and port mapping info
- Empty state message when no forwards are active
### Log Card
- Height reduced from 300dp to 250dp
- Monospace font at 10sp
- Log level filtering: WARN+ in release, all levels in debug
## Behavior Changes
### Copy-Tap
All value TextViews in the status card respond to tap with clipboard copy and "Disalin" Toast feedback.
### Log Level Filtering
`LogBuffer.initLevel(BuildConfig.DEBUG)` called in `onCreate()` — release builds only store WARN+ entries (50 max), debug builds store all.
### Port Forward Updates
`updatePortForwardCard()` called after every `updateCardFields()` in all tunnel states (CONNECTING, CONNECTED, FAILED).
## Layout IDs
| ID | Type | Purpose |
|----|------|---------|
| `switchVpn` | MaterialSwitch | VPN toggle |
| `tvStatus` | TextView | Connection status text |
| `cardInfo` | MaterialCardView | Status Device Agent card |
| `tvIpAddress` | TextView | Internal IP value |
| `tvAdminWeb` | TextView | Admin web domain (was `tvServerEndpoint`) |
| `tvDeviceId` | TextView | Device ID value |
| `tvHandshake` | TextView | Last handshake time |
| `tvAllowedIps` | TextView | Allowed IPs value |
| `tvTransport` | TextView | Transport protocol |
| `cardPorts` | MaterialCardView | Port Forwarding card |
| `portForwardContainer` | LinearLayout | Dynamic port forward items |
| `cardLog` | MaterialCardView | Log card |
| `tvLogScrollView` | ScrollView | Log scroll container |
| `tvLog` | TextView | Log text |
| `btnImportConfig` | MaterialButton | Import config button |
| `btnToggleLog` | MaterialButton | Log toggle button |
| `btnSettings` | ImageButton | Settings icon |
## Breaking Changes
- `tvServerEndpoint` renamed to `tvAdminWeb` — any code referencing the old ID must be updated
- New string resources added — translations needed for non-Indonesian locales
+64
View File
@@ -0,0 +1,64 @@
# NexusGuard SD-WAN Suite
> Enterprise Zero-Trust SD-WAN with WireGuard
NexusGuard is a production-grade, zero-trust SD-WAN solution built with Go, Vue 3, and WireGuard. It enables stealth VPN tunneling, centralized IPAM, and real-time network isolation via Linux `nftables` — all managed through a futuristic glassmorphism dashboard.
## Key Highlights
- **Zero-Attack Surface** — No public registration. Admin accounts created via CLI only.
- **Stealth Agent** — WireGuard config injected into memory. No files written to `/etc/wireguard/`.
- **Real-time Firewall** — Per-peer nftables rules synced to kernel instantly.
- **Cross-Platform** — Agent runs on Linux (daemon), Windows (tray + service), and macOS (tray).
## Tech Stack
`Go` · `Vue 3` · `WireGuard` · `PostgreSQL` · `Redis` · `nftables` · `gRPC` · `Docker`
## Architecture
```
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Dashboard │──HTTP──▶│ Server Core │◀─WG────▶│ Device Agent │
│ (Vue 3) │ :8080 │ (Go/Gin) │ :51820 │ (Go) │
└──────────────┘ │ │ └──────────────┘
│ Port 8080: │ │
│ - HTTP API │ ┌────┴────┐
│ - gRPC Signal │ │ TUN (wg)│
│ (cmux) │ │ Memory │
└─────────────────┘ └─────────┘
```
## Submodule Knowledge Bases
Each submodule has its own `AGENTS.md` with detailed architecture, conventions, and anti-patterns:
| Submodule | AGENTS.md | Scope |
|-----------|-----------|-------|
| [Server Core](apps/server-core/) | [AGENTS.md](apps/server-core/AGENTS.md) | API handlers, database models, firewall rules, gRPC signaling, WireGuard management |
| [Dashboard UI](apps/dashboard-ui/) | [AGENTS.md](apps/dashboard-ui/AGENTS.md) | Vue 3 components, Pinia stores, TailwindCSS styling, API client |
| [Device Agent](apps/device-agent/) | [AGENTS.md](apps/device-agent/AGENTS.md) | Go daemon, system tray, memory-injected WireGuard, heartbeat, gRPC signaling |
| [Android Agent](apps/android-agent/) | [AGENTS.md](apps/android-agent/AGENTS.md) | Kotlin VPNService, GoBackend tunnel, HTTP heartbeat, port forwarding |
**Rule**: When working on a submodule, always read its `AGENTS.md` first.
## Documentation
| Document | Description |
|----------|-------------|
| [Architecture](architecture.md) | System architecture, data flow, security model |
| [Tech Stack](tech-stack.md) | Technology breakdown per component |
| [Features](features.md) | Feature showcase and capabilities |
| [Deployment](deployment.md) | Deployment guide (Docker, native, development) |
## Quick Start
```bash
git clone https://git.datadunia.com/nexusguard/Nexus-Guard-Suite.git
cd Nexus-Guard-Suite
bash update.sh
```
## License
Private — DataDunia
+194
View File
@@ -0,0 +1,194 @@
# Architecture Overview
NexusGuard is a three-component SD-WAN system: a central API server, a web dashboard, and cross-platform device agents. All communication is encrypted. Tunnels are fileless. Access is zero-trust.
## High-Level Architecture
```
┌─────────────────────────────────────────────────────────────────────┐
│ Dashboard (Vue 3) │
│ Glassmorphism Web Interface │
│ Manages: Nodes, Devices, Rules │
└───────────────────────────────────┬─────────────────────────────────┘
│ HTTP (port 80)
┌─────────────────────────────────────────────────────────────────────┐
│ Nginx Reverse Proxy │
│ Routes: /api/ → :8080, / → SPA │
└───────────────────────────────────┬─────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Server Core (Go/Gin) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ HTTP API │ │ gRPC │ │ IPAM │ │ nftables │ │
│ │ (20+ │ │ Signaling│ │ Manager │ │ Firewall │ │
│ │ handlers)│ │ (cmux) │ │ │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL Database │ │
│ │ (wg_servers, devices, rules, users) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Redis │ │ WireGuard │ │
│ │ (Heartbeat TTL) │ │ (wg0 interface) │ │
│ └──────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ Device Agent (Linux) │ │ Device Agent (Win/Mac) │
│ Systemd Daemon │ │ System Tray │
│ Memory-injected WG │ │ Memory-injected WG │
└───────────────────────────┘ └───────────────────────────┘
```
## Component Breakdown
### Server Core
The central API and VPN hub. Written in Go with Gin framework.
| Responsibility | Implementation |
|----------------|----------------|
| API endpoints | 20+ Gin handlers (`api/` directory) |
| gRPC signaling | Bidirectional streaming via cmux (port 8080) |
| IPAM | IP pool allocation from CIDR per node |
| Firewall | nftables rule management (add/remove per peer) |
| WireGuard | Interface control, peer sync, config push |
| Auth | JWT middleware, admin-only enforcement |
| Heartbeat | Config sync, handshake monitoring |
### Dashboard UI
Admin web interface. Built with Vue 3 and glassmorphism design system.
| Responsibility | Implementation |
|----------------|----------------|
| Node management | Register/edit WireGuard servers |
| Device management | CRUD, provisioning tokens, QR codes |
| Firewall rules | Per-peer nftables rule editor |
| Live telemetry | 10s polling for device health |
| Traffic history | Time-range filtering, export |
### Device Agent
Stealth VPN daemon. Cross-platform Go binary.
| Responsibility | Implementation |
|----------------|----------------|
| Provisioning | HTTP POST with AES-256-GCM encrypted response |
| Tunnel | Memory-injected WireGuard (no disk files) |
| Heartbeat | HTTP/gRPC, config sync, handshake monitoring |
| gRPC | Bidirectional stream for real-time commands |
| Self-healing | Exponential backoff reconnection |
## Data Flow
### Provisioning Flow
```
1. Admin creates device via Dashboard → API generates registration token
2. Agent sends token + HWID to POST /api/v1/provision
3. Server validates token, allocates IP from pool
4. Server responds with WireGuard config (AES-256-GCM encrypted)
5. Agent decrypts config, injects into WireGuard via IpcSet
6. Tunnel established — no files written to disk
```
### Heartbeat Flow
```
Every 30 seconds:
1. Agent reads last_handshake_time from WireGuard IPC
2. Agent POSTs {device_id, tunnel_up, last_handshake} to server
3. Server loads Device + WgServer fresh from DB (dynamic, not cached)
4. Server computes config_hash = SHA256(tunnelFields) + ":" + SHA256(forwards)
- Tunnel fields: server_pub, endpoint, internal_ip, private_key, preshared_key, allowed_ips, dns
- Forwards: sorted protocol:publicPort->targetIP:targetPort:ID
5. Server responds with full config + config_hash
6. Agent compares config_hash with previous → if different → rebuild tunnel/reload forwards
```
> **Design**: Peers (devices) store only their own data (keys, IP, settings). Node data (endpoint, public key) is loaded fresh from DB on every heartbeat. This ensures config always reflects the latest node state without requiring agent restart.
### Suspend/Resume Flow
```
Suspend:
1. Admin clicks "Suspend" in Dashboard
2. Dashboard POSTs /api/v1/devices/:id/suspend
3. Server updates DB (is_suspended = true)
4. Server sends gRPC SuspendCommand to agent
5. Server removes WireGuard peer from kernel
6. Agent receives command → stops tunnel
Resume:
1. Admin clicks "Resume" in Dashboard
2. Server updates DB (is_suspended = false)
3. Server re-adds WireGuard peer to kernel
4. Server sends gRPC ResumeCommand with full ConfigUpdate
5. Agent receives command → rebuilds tunnel
```
## Security Model
### Zero-Trust Principles
| Principle | Implementation |
|-----------|----------------|
| No public registration | `/auth/register` locked; admin via CLI only |
| Encrypted provisioning | AES-256-GCM for WireGuard config transfer |
| Fileless tunnels | WireGuard config in process memory only |
| Hardware binding | HWID (DMI/CPU serial) bound to registration token |
| Per-peer isolation | nftables rules per device, default deny |
| JWT authentication | All API endpoints require valid token |
### Trust Boundaries
```
┌─────────────────────────────────────────────────────────┐
│ Trusted Zone │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ Server │ │ Database │ │ WireGuard Interface │ │
│ │ Core │ │ (PG) │ │ (wg0) │ │
│ └──────────┘ └──────────┘ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Encrypted Channel
(AES-256-GCM / WG)
┌─────────────────────────────────────────────────────────┐
│ Untrusted Zone │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Device Agent │ │
│ │ (Memory-only WireGuard config) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
```
## Port Multiplexing
Server Core uses cmux to serve both HTTP and gRPC on port 8080:
```go
m := cmux.New(lis)
grpcLis := m.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings(
"content-type", "application/grpc",
))
httpLis := m.Match(cmux.Any())
```
- gRPC matched by `content-type: application/grpc` header
- HTTP matched by `Any()` (catch-all)
- Single port, single listener, zero extra config
+476
View File
@@ -0,0 +1,476 @@
# Deployment Guide
NexusGuard supports three deployment modes: Docker (recommended), native install, and development.
## Docker Deployment (Recommended)
### Prerequisites
- Docker 20.10+
- Docker Compose v2
- Git
### Quick Start
```bash
# Clone repository
git clone https://git.datadunia.com/nexusguard/Nexus-Guard-Suite.git
cd Nexus-Guard-Suite
# Start all services (auto-generates .env on first run)
bash update.sh
```
### First Boot
On first run, the system automatically:
1. Pulls latest code and builds Docker containers
2. Generates Local Primary Node WireGuard keys
3. Creates database schema via migration
### Configuration
Edit `.env` in root directory:
```bash
# Database
DB_HOST=postgres
DB_PORT=5432
DB_USER=nexusguard
DB_PASSWORD=your_secure_password
DB_NAME=nexusguard
# Redis
REDIS_ADDR=redis:6379
# Security (auto-generated by update.sh)
JWT_SECRET=<hex-64-chars>
SERVER_SALT=<hex-64-chars>
# Network
NFTABLES_TABLE=nexusguard
IPAM_POOL=10.8.0.0/16
# Server
GIN_MODE=release
PORT=8080
# Dashboard
VITE_API_BASE_URL=https://api.yourdomain.com/api/v1
```
### Update Commands
```bash
bash update.sh # Smart update (rebuild only if changes)
bash update.sh --force # Force rebuild
bash update.sh --backup # Backup PostgreSQL before update
bash update.sh --no-migrate # Skip database migration
```
### Makefile Commands
| Command | Description |
|---------|-------------|
| `make up` | Start all services |
| `make down` | Stop all services |
| `make logs` | Tail all service logs |
| `make dev` | Start with hot-reload |
| `make migrate` | Run database migration |
| `make reset-db` | Reset database to initial state |
### Create Admin Account
```bash
docker exec -it nexus-guard-suite-server-core-1 ./server-core \
-create-admin -user admin -pass "YourSecurePassword123!"
```
### Service Architecture
```
┌─────────────────────────────────────────────────┐
│ Docker Compose │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ nginx │ │ server- │ │ postgres │ │
│ │ :80/:443 │→ │ core │→ │ :5432 │ │
│ │ │ │ :8080 │ │ │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ ↑ ↑ │
│ ┌──────────┐ ┌──────────┐ │
│ │ dashboard│ │ redis │ │
│ │ (static) │ │ :6379 │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────┘
```
### Port Mapping
| Service | Container Port | Host Port |
|---------|----------------|-----------|
| nginx | 80 | 80 |
| nginx | 443 | 443 |
| server-core | 8080 | 8080 |
| postgres | 5432 | 5432 |
| redis | 6379 | 6379 |
### Volumes
| Volume | Purpose |
|--------|---------|
| `postgres_data` | PostgreSQL data persistence |
| `redis_data` | Redis data persistence |
---
## Native Install
For production servers without Docker.
### Prerequisites
**Debian/Ubuntu:**
```bash
sudo apt install -y golang nginx postgresql redis-server nftables wireguard-tools
```
**CentOS/Rocky:**
```bash
sudo dnf install -y golang nginx postgresql-server redis nftables wireguard-tools
```
### Build Binaries
**Server Core:**
```bash
cd apps/server-core
CGO_ENABLED=0 go build -o ../../bin/server-core .
cd ../..
```
**Dashboard UI:**
```bash
cd apps/dashboard-ui
npm install
VITE_API_BASE_URL=/api/v1 npm run build
cd ../..
```
### Run Installer
```bash
sudo bash nexusguard-install.sh
```
**Options:**
```bash
sudo bash nexusguard-install.sh --server-port 8080 --web-port 80
sudo bash nexusguard-install.sh --db-host 127.0.0.1 --db-pass mypassword
```
**What the installer does:**
1. Creates PostgreSQL database and user
2. Installs binary to `/usr/local/bin/nexusguard-server`
3. Installs dashboard to `/usr/share/nexusguard/dashboard/`
4. Creates config at `/etc/nexusguard/nexusguard.conf`
5. Runs database migration
6. Creates systemd service
7. Configures nginx
### Create Admin Account
```bash
sudo /usr/local/bin/nexusguard-server \
-create-admin -user admin -pass "YourSecurePassword123!"
```
### Service Management
```bash
# Start
sudo systemctl start nexusguard-server
# Stop
sudo systemctl stop nexusguard-server
# Status
sudo systemctl status nexusguard-server
# Logs
sudo journalctl -u nexusguard-server -f
```
### Configuration
Edit `/etc/nexusguard/nexusguard.conf`:
```bash
# Database
DB_HOST=127.0.0.1
DB_PORT=5432
DB_USER=nexusguard
DB_PASSWORD=nexusguard
DB_NAME=nexusguard
# Redis
REDIS_ADDR=127.0.0.1:6379
# Security (auto-generated)
JWT_SECRET=<hex-64-chars>
SERVER_SALT=<hex-64-chars>
# Network
NFTABLES_TABLE=nexusguard
IPAM_POOL=10.8.0.0/16
# Server
GIN_MODE=release
PORT=8080
```
### Uninstall
```bash
# Remove files only
sudo bash nexusguard-uninstall.sh
# Also drop database
sudo bash nexusguard-uninstall.sh --remove-db
```
---
## Development Mode
For local development with hot-reload.
### Prerequisites
- Go 1.25+
- Node.js 24+
- PostgreSQL
- Redis
### Setup Database
1. Create PostgreSQL database:
```sql
CREATE DATABASE nexusguard;
CREATE USER nexusguard WITH PASSWORD 'nexusguard';
GRANT ALL PRIVILEGES ON DATABASE nexusguard TO nexusguard;
```
2. Copy environment template:
```bash
cp .env.example .env
```
3. Edit `.env` with your database credentials.
### Start Backend
```bash
cd apps/server-core
go mod download
go run -tags dev .
```
The `-tags dev` flag:
- Runs AutoMigrate on startup
- Provisions local node
- Enables debug logging
### Start Frontend
```bash
cd apps/dashboard-ui
npm install
npm run dev
```
Dashboard available at `http://localhost:5173`.
### Create Admin Account
```bash
cd apps/server-core
go run -tags dev . -create-admin -user admin -pass "YourNewSecurePassword123!"
```
### Development Workflow
```
┌─────────────────────────────────────────────────────────┐
│ Development Setup │
│ │
│ Terminal 1: Backend │
│ ┌───────────────────────────────────────────────────┐ │
│ │ $ go run -tags dev . │ │
│ │ [dev] AutoMigrate complete │ │
│ │ [dev] Local node provisioned │ │
│ │ [gin] Listening on :8080 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ Terminal 2: Frontend │
│ ┌───────────────────────────────────────────────────┐ │
│ │ $ npm run dev │ │
│ │ │ │
│ │ VITE v8.0.0 ready in 300 ms │ │
│ │ │ │
│ │ ➜ Local: http://localhost:5173/ │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ Browser: http://localhost:5173 │
│ → Dashboard UI (Vue 3 + Vite) │
│ → API calls proxied to :8080 │
└─────────────────────────────────────────────────────────┘
```
---
## Agent Installation
### Linux (Automated)
```bash
# Transfer script
scp scripts/install_agent.sh user@target-machine:~
# Run installer
sudo ./install_agent.sh \
--server-url "https://api.yourdomain.com" \
--token "REG_TOKEN_FROM_DASHBOARD"
# Verify
sudo systemctl status sys-bridge.service
```
**Options:**
- `--binary-name "my-agent"` — Override default binary name
**What the script does:**
1. Detects OS (APT/YUM)
2. Installs dependencies (iproute2, curl)
3. Downloads correct binary for architecture
4. Creates config at `~/.config/nexusguard/nexusguard.conf`
5. Creates systemd service
6. Starts agent
### Linux (Manual)
```bash
# Download binary
sudo cp nexusguard-device-agent-linux-amd64 /usr/local/bin/sys-bridge
sudo chmod +x /usr/local/bin/sys-bridge
# Create config
mkdir -p ~/.config/nexusguard
cat > ~/.config/nexusguard/nexusguard.conf <<EOF
SERVER_URL=https://api.yourdomain.com
REG_TOKEN=YOUR_REG_TOKEN
EOF
# Create systemd service
sudo tee /etc/systemd/system/sys-bridge.service > /dev/null <<EOF
[Unit]
Description=NexusGuard Device Agent
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/sys-bridge
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Start
sudo systemctl daemon-reload
sudo systemctl enable --now sys-bridge.service
```
### Windows
1. Build GUI binary:
```bash
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \
-ldflags="-s -w -H windowsgui" \
-o nexusguard-device-agent-gui.exe .
```
2. Place `wintun.dll` next to executable
3. Run agent — config auto-created at `%ProgramData%\NexusGuard\nexusguard.conf`
4. Edit config with `SERVER_URL` and `REG_TOKEN`
5. Right-click tray icon → Connect
**Service Management:**
- Install as Service — Register Windows service
- Uninstall Service — Remove Windows service
- Start on Boot — Toggle auto-start
### Docker
```bash
docker build -t nexus-device-agent .
docker run -d \
--name nexus-agent \
--restart unless-stopped \
--network host \
--cap-add NET_ADMIN \
--cap-add NET_RAW \
-e SERVER_URL=https://api.yourdomain.com \
-e REG_TOKEN=YOUR_REG_TOKEN \
nexus-device-agent
```
**Required capabilities:**
- `NET_ADMIN` — Network interface configuration
- `NET_RAW` — Raw socket access (WireGuard)
---
## Troubleshooting
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| Agent fails to start | Missing config | Check config file has `SERVER_URL` + `REG_TOKEN` |
| Provisioning fails | Invalid token | Generate new token from Dashboard |
| Tunnel won't create | Missing kernel module | `sudo modprobe wireguard` |
| `ip addr add` fails | Permissions | Run as root or grant `CAP_NET_ADMIN` |
| Heartbeat timeout | Network issue | `curl -I <SERVER_URL>` |
### Log Locations
| Platform | Location |
|----------|----------|
| Docker | `docker logs -f nexus-guard-suite-server-core-1` |
| Native | `sudo journalctl -u nexusguard-server -f` |
| Agent (Linux) | `~/.local/share/nexusguard/logs/` |
| Agent (Windows) | `%ProgramData%\NexusGuard\logs\` |
| Agent (macOS) | `~/Library/Logs/NexusGuard/` |
### Debug Commands
```bash
# Check WireGuard interface
sudo wg show
# Test server connectivity
curl -I https://api.yourdomain.com/api/health
# Check agent version
/usr/local/bin/sys-bridge -version
# Run agent in foreground
sudo /usr/local/bin/sys-bridge -debug
```
+299
View File
@@ -0,0 +1,299 @@
# Features
NexusGuard provides enterprise-grade SD-WAN capabilities with a focus on security, automation, and ease of use.
## Zero-Trust Security
### No Public Registration
The `/auth/register` endpoint is locked. Admin accounts can only be created via CLI:
```bash
# Docker
docker exec -it nexus-guard-suite-server-core-1 ./server-core \
-create-admin -user admin -pass "SecurePassword123!"
# Native
sudo /usr/local/bin/nexusguard-server \
-create-admin -user admin -pass "SecurePassword123!"
```
**Why?** Eliminates the attack surface of open registration. No bots, no brute force, no unauthorized accounts.
### Encrypted Provisioning
Agent provisioning uses AES-256-GCM encryption:
1. Agent sends registration token + hardware ID (HWID)
2. Server generates WireGuard config
3. Config encrypted with AES-256-GCM before transmission
4. Agent decrypts in memory, never touches disk
**Why?** WireGuard keys are sensitive. Encryption in transit prevents interception even on compromised networks.
### Memory-Injected Tunnels
WireGuard configuration is injected directly into the kernel via `IpcSet`:
```
Traditional: Config file → /etc/wireguard/wg0.conf → wg-quick up wg0
NexusGuard: Config bytes → IpcSet() → Tunnel active (no files)
```
**Benefits:**
- No config files to steal
- No lingering configs after disconnect
- Multiple agents can run without conflicts
- Clean uninstall = kill process
### Hardware ID Binding
Each agent is bound to its hardware via HWID:
- **Linux:** `/sys/class/dmi/id/product_uuid` or CPU serial
- **Windows:** DMI product UUID
- **macOS:** IOPlatformSerialNumber
HWID is included in provisioning request. Server validates before issuing config.
## Multi-Node Support
### Geographic Scaling
Deploy WireGuard servers across multiple regions:
```
Node 1 (Singapore): 10.172.21.0/24
Node 2 (Frankfurt): 10.172.22.0/24
Node 3 (Virginia): 10.172.23.0/24
```
Each node has its own:
- IP pool (CIDR)
- Interface address
- Peer defaults (DNS, MTU, Keepalive)
- Endpoint (IP/Domain + Port)
### Centralized IPAM
IP Address Management is centralized in the database:
1. Admin defines IP pool per node (e.g., `10.172.21.0/24`)
2. When device is created, server allocates next available IP
3. IP is reserved in database (no duplicates)
4. IP is released when device is deleted
**Why?** Prevents IP conflicts across nodes. Enables static IP assignment for critical devices.
### Per-Node Defaults
Each node can have different peer defaults:
| Setting | Node 1 (SG) | Node 2 (DE) |
|---------|-------------|-------------|
| DNS | `1.1.1.1` | `8.8.8.8` |
| MTU | 1420 | 1280 |
| Keepalive | 25s | 0s |
| AllowedIPs | `10.172.21.0/24` | `0.0.0.0/0` |
Devices inherit from their node, with per-device overrides available.
## Real-Time Firewall
### nftables Integration
NexusGuard manages Linux nftables directly:
```bash
# What NexusGuard creates in the kernel
table ip nexusguard {
set peers_v4 {
type ipv4_addr
elements = { 10.172.21.2, 10.172.21.3, ... }
}
chain forward {
type filter hook forward priority 0; policy accept;
ip daddr @peers_v4 accept
ip saddr @peers_v4 accept
drop
}
}
```
### Per-Peer Rules
Each device can have custom firewall rules:
- **Allow/Block IP ranges** — `192.168.1.0/24`, `10.0.0.1`
- **Port filtering** — TCP/UDP port ranges
- **Direction control** — Inbound, outbound, or both
Changes are synced to kernel instantly — no restart required.
### Default SSH Provisioning
New peers automatically get SSH access (port 22):
```go
// Automatically added on peer creation
AddFirewallRule(peerIP, "0.0.0.0/0", 22, "tcp", "allow")
```
**Why?** Ensures remote access isn't accidentally locked out.
## Cross-Platform Agent
### Linux — Systemd Daemon
```bash
# Automated install
sudo ./install_agent.sh \
--server-url "https://api.yourdomain.com" \
--token "REG_TOKEN"
# Verify
sudo systemctl status sys-bridge.service
```
Features:
- Runs as root (required for WireGuard)
- Auto-restart on failure
- Journal logging
- Config at `~/.config/nexusguard/nexusguard.conf`
### Windows — System Tray + Service
System tray application with service management:
| Menu Item | Action |
|-----------|--------|
| Status | Shows Connected/Disconnected |
| IP | Shows internal VPN IP |
| Connect | Start tunnel |
| Disconnect | Stop tunnel |
| Install as Service | Register Windows service |
| Start on Boot | Toggle auto-start |
### macOS — System Tray
System tray application (no service support):
- Config at `~/Library/Application Support/NexusGuard/nexusguard.conf`
- Logs at `~/Library/Logs/NexusGuard/`
### Self-Healing
All platforms implement exponential backoff:
```
Failure 1: Wait 30s, retry
Failure 2: Wait 60s, retry
Failure 3: Wait 120s, retry
...
Failure N: Wait 300s (max), retry
```
Network drops are handled gracefully — tunnel stays alive, agent reconnects in background.
## Dashboard
### Glassmorphism Design
Futuristic UI with glass-like transparency:
- Backdrop blur effects
- Semi-transparent panels
- Gradient accents
- Smooth animations
### Live Telemetry
Device health polled every 10 seconds:
```
┌─────────────────────────────────────────┐
│ Device: server-01 │
│ Status: ● Online │
│ IP: 10.172.21.2 │
│ Last Handshake: 15s ago │
│ Uptime: 3d 14h 22m │
└─────────────────────────────────────────┘
```
### Server-Side Config Sync
Config changes (firewall rules, AllowedIPs, DNS, endpoint) are detected via SHA256 hash comparison:
- Server computes `config_hash = SHA256(tunnelFields) + ":" + SHA256(forwards)`
- Agent compares with previous hash → if different → tunnel rebuilds automatically
- Works over both HTTP heartbeat (30s) and gRPC (immediate)
- Agent never caches config — server loads fresh from DB each heartbeat
### QR Code Setup
Generate QR codes for mobile WireGuard clients:
1. Create device in Dashboard
2. Click "Show QR Code"
3. Scan with WireGuard app on iOS/Android
4. Tunnel ready — no manual config
### Share Links
Time-limited config sharing:
1. Click "Generate Share Link"
2. Set expiration (1h, 24h, 7d)
3. Share URL with recipient
4. Recipient downloads `.conf` file
5. Link expires automatically
## Deployment Flexibility
### Docker (Recommended)
```bash
git clone https://git.datadunia.com/nexusguard/Nexus-Guard-Suite.git
cd Nexus-Guard-Suite
bash update.sh
```
One command to start everything. Auto-migration on first boot.
### Native Install
For servers without Docker:
```bash
sudo bash nexusguard-install.sh
```
Creates systemd service, nginx config, PostgreSQL database.
### Development Mode
Hot-reload for both backend and frontend:
```bash
# Terminal 1: Backend
cd apps/server-core
go run -tags dev .
# Terminal 2: Frontend
cd apps/dashboard-ui
npm run dev
```
Auto-migration on startup. No Docker required.
## Comparison
| Feature | NexusGuard | Traditional VPN | Commercial SD-WAN |
|---------|------------|-----------------|-------------------|
| Zero-trust | ✅ | ❌ | ✅ |
| Fileless tunnel | ✅ | ❌ | ❌ |
| Multi-platform agent | ✅ | Partial | ✅ |
| Real-time firewall | ✅ | ❌ | ✅ |
| Self-hosted | ✅ | ✅ | ❌ |
| Open source | ✅ | ✅ | ❌ |
| Cost | Free | Free | $$$$ |
+134
View File
@@ -0,0 +1,134 @@
# Technology Stack
NexusGuard uses a modern, production-grade technology stack. Each component is built with tools optimized for its domain.
## Backend — Server Core
| Technology | Version | Purpose |
|------------|---------|---------|
| Go | 1.25+ | Primary language |
| Gin | 1.12 | HTTP framework |
| GORM | 1.31 | ORM (PostgreSQL) |
| grpc-go | latest | gRPC signaling |
| cmux | latest | Port multiplexing (HTTP + gRPC on :8080) |
| go-redis | 9.x | Heartbeat TTL cache |
| google/nftables | 0.3 | Linux firewall management |
| wgctrl | latest | WireGuard interface control |
| JWT v5 | latest | Authentication tokens |
### Why Go?
- **Static binaries** — No runtime dependencies, easy deployment
- **Concurrency** — Goroutines for handling 10000+ concurrent agent connections
- **WireGuard ecosystem** — Native Go WireGuard libraries (wgctrl, wireguard-go)
- **Performance** — Low memory footprint, fast cold start
### Why cmux?
Single port for HTTP and gRPC eliminates:
- Firewall rules for multiple ports
- Load balancer complexity
- Docker port mapping overhead
## Frontend — Dashboard UI
| Technology | Version | Purpose |
|------------|---------|---------|
| Vue | 3.5 | UI framework (Composition API) |
| Vite | 8 | Build tool + dev server |
| TypeScript | 6.0 | Type safety |
| TailwindCSS | 4.3 | Styling (glassmorphism design system) |
| Pinia | 2.3 | State management |
| Axios | 1.16 | HTTP client |
| HeadlessUI | 1.7 | Accessible UI primitives |
| Iconify | 5.0 | Icon system |
| VueUse | 14.3 | Composition utilities |
### Why Vue 3?
- **Composition API** — Better TypeScript support, reusable logic via composables
- **`<script setup>`** — Cleaner SFC syntax, less boilerplate
- **Ecosystem** — Mature ecosystem with Pinia, Vue Router, VueUse
### Why TailwindCSS 4?
- **Design tokens** — `@theme` block for consistent colors, spacing, typography
- **Glassmorphism** — Utility classes for backdrop-blur, transparency, gradients
- **No PostCSS** — Uses `@tailwindcss/vite` plugin (faster builds)
## Client — Device Agent
| Technology | Version | Purpose |
|------------|---------|---------|
| Go | 1.25+ | Primary language |
| wireguard-go | latest | Userspace WireGuard |
| fyne.io/systray | latest | Cross-platform system tray |
| wintun | latest | Windows WireGuard driver |
### Cross-Compile Targets
| Platform | Architecture | Binary |
|----------|--------------|--------|
| Linux | amd64 | `nexusguard-device-agent-linux-amd64` |
| Linux | arm64 | `nexusguard-device-agent-linux-arm64` |
| Linux | arm | `nexusguard-device-agent-linux-arm` |
| Windows | amd64 | `nexusguard-device-agent-gui.exe` |
| macOS | arm64 | `nexusguard-device-agent-darwin-arm64` |
| macOS | amd64 | `nexusguard-device-agent-darwin-amd64` |
### Why Memory-Injected Tunnels?
Traditional WireGuard setups write config to `/etc/wireguard/`. NexusGuard avoids this:
1. **Security** — No config files on disk = no file theft risk
2. **Stealth** — Tunnel exists only in process memory
3. **Cleanup** — Process exit = tunnel gone (no lingering configs)
4. **Multi-tenant** — Multiple agents can run without config conflicts
## Infrastructure
| Technology | Purpose |
|------------|---------|
| PostgreSQL | Primary database (wg_servers, devices, rules, users) |
| Redis | Heartbeat TTL cache, session tracking |
| nginx | Reverse proxy, SPA fallback, API routing |
| Docker | Container orchestration |
| docker-compose | Multi-service deployment |
### Why PostgreSQL?
- **ACID compliance** — Critical for IPAM allocation (no duplicate IPs)
- **JSON support** — Flexible config storage
- **Maturity** — Battle-tested for production workloads
### Why Redis?
- **Heartbeat TTL** — Fast expiry checks for agent health
- **Session cache** — gRPC session tracking
- **Pub/Sub** — Real-time event distribution (future)
## Build & CI
| Tool | Purpose |
|------|---------|
| Go build | Static binary compilation |
| Vite build | SPA bundling (vue-tsc + vite) |
| Gitea Actions | CI/CD per submodule |
| Makefile | Quick commands (up, down, dev, migrate) |
### Build Flags
```bash
# Server Core (production)
CGO_ENABLED=0 go build -o server-core .
# Device Agent (stripped)
go build -ldflags="-s -w" -o device-agent .
# Dashboard UI
VITE_API_BASE_URL=/api/v1 npm run build
```
- `CGO_ENABLED=0` — Static binary, no CGO dependencies
- `-ldflags="-s -w"` — Strip debug symbols (~30% smaller binary)
- `VITE_API_BASE_URL` — Build-time API endpoint injection
@@ -0,0 +1,209 @@
# NexusGuard Portfolio Design Spec
**Date:** 2026-06-21
**Status:** Draft
**Author:** Sisyphus
## Overview
Create a comprehensive technical portfolio for the NexusGuard SD-WAN Suite project, showcasing architecture, technology stack, features, and deployment options. Target audience: technical recruiters, potential clients, and fellow developers.
## Goals
1. Present NexusGuard as a production-grade enterprise SD-WAN solution
2. Highlight unique selling points (zero-trust, stealth agent, memory-injected tunnels)
3. Provide clear architecture overview for technical audience
4. Document technology choices and reasoning
5. Showcase deployment flexibility (Docker, native, development)
## Scope
### In Scope
- Multi-file Markdown portfolio in `docs/portfolio/`
- 5 files: README, architecture, tech-stack, features, deployment
- ASCII/text-based diagrams (no external dependencies)
- Internal navigation links between files
- Focus on technical depth over marketing fluff
### Out of Scope
- Interactive VitePress integration (future enhancement)
- Screenshots/demo embeds (requires running instances)
- Video content
- Multi-language support (Indonesian/English)
## Design Decisions
### File Structure
```
docs/portfolio/
├── README.md # Landing page / entry point
├── architecture.md # System architecture + diagrams
├── tech-stack.md # Technology breakdown per component
├── features.md # Feature showcase
└── deployment.md # Deployment guide
```
### Diagram Style
Use ASCII/text-based diagrams for portability and version control friendliness. Examples:
```
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Dashboard │──HTTP──▶│ Server Core │◀─WG────▶│ Device Agent │
│ (Vue 3) │ :8080 │ (Go/Gin) │ :51820 │ (Go) │
└──────────────┘ └─────────────────┘ └──────────────┘
```
### Content Tone
Technical and precise. Avoid marketing buzzwords. Focus on:
- What it does (functional description)
- How it works (architecture/implementation)
- Why these choices (technology rationale)
## File Specifications
### 1. README.md (Landing Page)
**Purpose:** Quick overview, entry point to other sections
**Content:**
- Project name + tagline: "Enterprise Zero-Trust SD-WAN with WireGuard"
- 2-3 sentence description
- Key highlights (3-4 bullet points):
- Zero-attack surface (no public registration)
- Stealth agent (memory-injected, no disk files)
- Real-time nftables isolation
- Cross-platform (Linux, Windows, macOS)
- Tech badges: Go, Vue 3, WireGuard, PostgreSQL, Docker
- Navigation links to other portfolio files
**Length:** ~100 lines
### 2. architecture.md (System Architecture)
**Purpose:** Technical deep-dive into system design
**Content:**
- High-level architecture diagram (ASCII)
- Component breakdown:
- Server Core: API, gRPC signaling, IPAM, firewall, WireGuard management
- Dashboard UI: Management interface, glassmorphism design
- Device Agent: Stealth daemon, multi-platform, self-healing
- Data flow:
- Provisioning flow (HTTP → AES-256-GCM → memory injection)
- Heartbeat flow (HTTP/gRPC, config sync, handshake monitoring)
- Suspend/Resume flow (gRPC command → tunnel control)
- Security model:
- Zero-trust (admin CLI only, no public register)
- Encrypted provisioning (AES-256-GCM)
- Fileless tunnels (WireGuard config in RAM)
- Hardware binding (HWID detection)
**Length:** ~250 lines
### 3. tech-stack.md (Technology Breakdown)
**Purpose:** Detailed technology choices per component
**Content:**
- Backend (Server Core):
- Go 1.25+, Gin framework, GORM ORM
- PostgreSQL, Redis
- grpc-go + cmux (port multiplexing)
- nftables (google/nftables), WireGuard (wgctrl)
- Frontend (Dashboard UI):
- Vue 3.5 (Composition API, `<script setup>`)
- Vite 8, TypeScript 6.0
- TailwindCSS 4.3 (glassmorphism design system)
- Pinia 2.3, Axios 1.16, HeadlessUI 1.7
- Client (Device Agent):
- Go 1.25+, fyne.io/systray
- WireGuard (memory-injected via IpcSet)
- Cross-compile targets: linux/amd64, arm64, arm; windows/amd64; darwin/arm64, amd64
- Infrastructure:
- PostgreSQL, Redis, nginx
- Docker + docker-compose
- Gitea Actions (CI/CD)
**Length:** ~200 lines
### 4. features.md (Feature Showcase)
**Purpose:** Highlight key capabilities
**Content:**
- Zero-Trust Security:
- No public registration (admin CLI only)
- AES-256-GCM encrypted provisioning
- Memory-injected WireGuard (no /etc/wireguard/ files)
- Hardware ID binding (HWID)
- Multi-Node Support:
- Geographic scaling (multiple WG servers)
- Centralized IPAM (IP pool allocation per node)
- Per-node peer defaults
- Real-time Firewall:
- nftables integration (Linux kernel)
- Per-peer rules (IP/CIDR + port ranges)
- Instant sync to kernel (no restart)
- Cross-Platform Agent:
- Linux (systemd daemon)
- Windows (system tray + service)
- macOS (system tray)
- Self-healing (exponential backoff reconnection)
- Dashboard:
- Futuristic glassmorphism UI
- Live telemetry (10s polling)
- QR code for mobile setup
- Time-limited share links
- Deployment Flexibility:
- Docker (recommended)
- Native install (systemd + nginx)
- Development mode (hot-reload)
**Length:** ~200 lines
### 5. deployment.md (Deployment Guide)
**Purpose:** Practical deployment instructions
**Content:**
- Docker (Recommended):
- Quick start (bash update.sh)
- Configuration (.env)
- Makefile commands (up, down, logs, dev, migrate, reset-db)
- Native Install:
- Prerequisites (Go, Node.js, PostgreSQL, Redis, nginx, nftables, wireguard-tools)
- Build binaries
- Run installer (nexusguard-install.sh)
- Create admin account
- Development:
- Local setup (database + .env)
- Hot-reload (go run -tags dev, npm run dev)
- Database migration
- Agent Installation:
- Linux (automated install_agent.sh)
- Windows (manual + service setup)
- Docker container
**Length:** ~250 lines
## Success Criteria
1. All 5 files created in `docs/portfolio/`
2. Each file is self-contained (can be read independently)
3. Internal links work between files
4. ASCII diagrams render correctly in Markdown viewers
5. Content is accurate and matches actual codebase
6. No broken links or references
## Implementation Notes
- Use existing README content as base (avoid duplication)
- Extract key information from AGENTS.md files
- Verify technical details against actual code
- Keep diagrams simple and readable
- Use consistent Markdown formatting across all files
+24
View File
@@ -49,6 +49,7 @@ Prerequisites:
- nginx installed and running - nginx installed and running
- PostgreSQL installed and running - PostgreSQL installed and running
- Redis installed and running - Redis installed and running
- WireGuard tools (will be auto-installed if missing)
- Pre-built binaries in ./bin/ directory - Pre-built binaries in ./bin/ directory
This script will: This script will:
@@ -90,6 +91,29 @@ command -v systemctl >/dev/null 2>&1 || error "systemctl is not installed"
command -v psql >/dev/null 2>&1 || error "PostgreSQL client (psql) is not installed" command -v psql >/dev/null 2>&1 || error "PostgreSQL client (psql) is not installed"
command -v redis-cli >/dev/null 2>&1 || error "Redis client (redis-cli) is not installed" command -v redis-cli >/dev/null 2>&1 || error "Redis client (redis-cli) is not installed"
# Check and install WireGuard
if ! command -v wg >/dev/null 2>&1; then
warn "WireGuard tools not found. Installing..."
if command -v apt >/dev/null 2>&1; then
apt update -qq && apt install -y -qq wireguard-tools
elif command -v dnf >/dev/null 2>&1; then
dnf install -y -q wireguard-tools
elif command -v yum >/dev/null 2>&1; then
yum install -y -q wireguard-tools
elif command -v pacman >/dev/null 2>&1; then
pacman -S --noconfirm wireguard-tools
else
error "Cannot auto-install WireGuard. Please install wireguard-tools manually."
fi
info "WireGuard tools installed: $(wg --version)"
fi
# Check WireGuard kernel module
if ! lsmod 2>/dev/null | grep -q wireguard; then
warn "WireGuard kernel module not loaded. Loading..."
modprobe wireguard 2>/dev/null || warn "Could not load wireguard module (may need manual load or reboot)"
fi
# Check for pre-built binaries # Check for pre-built binaries
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_BINARY="$SCRIPT_DIR/bin/server-core" SERVER_BINARY="$SCRIPT_DIR/bin/server-core"
+103
View File
@@ -0,0 +1,103 @@
# ESP32 Agent — Architecture Reference
**Status**: ARCHITECTURE ONLY (no code)
**Location**: `reference/esp32-agent/` (NOT a submodule)
## Overview
This directory contains the architecture design and documentation for the ESP32-based WireGuard agent. It is **NOT a git submodule** — it is an architecture reference for future implementation.
## Why `reference/` not `apps/`?
If ESP32 agent is implemented as a submodule in the future, it will be added to `apps/device-agent-embedded/`. This `reference/` directory stores the architecture docs separately to avoid conflicts.
## Purpose
- Document the ESP32 agent architecture
- Define the API contract (shared with server-core)
- Hardware reference for LW840X module
- Track implementation status and TODOs
## When to Implement
When ready to build the ESP32 agent:
1. Create a new repo: `nexus-agent-embedded`
2. Add as submodule: `apps/device-agent-embedded/`
3. Use ESP-IDF v5.2+ toolchain
4. Follow the architecture in `docs/DESIGN.md`
## Files
| File | Description |
|------|-------------|
| `docs/DESIGN.md` | Technical design document (full architecture) |
| `docs/HARDWARE.md` | ESP32/LW840X hardware reference |
| `docs/API_COMPAT.md` | Server API contract (shared protocol) |
| `docs/ARCHITECTURE.md` | Module architecture + implementation status |
## Architecture Summary
```
┌─────────────────────────────────────────────────────────┐
│ ESP32 (FreeRTOS) │
├─────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ provision│ │ heartbeat│ │ tunnel │ │ config │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ crypto.c │ │
│ │ AES-256-GCM decrypt (mbedtls) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Network Stack (lwIP) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ WireGuard (lwIP socket API) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Hardware Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ WiFi │ │ NVS │ │ UART │ │ GPIO │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Implementation Status
| Module | Status | Notes |
|--------|--------|-------|
| `tunnel.c` | ❌ STUB | Needs `wireguard-esp32` component integration |
| `heartbeat.c` | ✅ Real | HTTP loop working, config sync TODO |
| `provision.c` | ✅ Real | HTTP provisioning working |
| `crypto.c` | ✅ Real | mbedtls AES-256-GCM |
| `config.c` | ✅ Real | NVS storage |
| `wifi.c` | ✅ Real | WiFi STA management |
## Dependencies
- **ESP-IDF**: v5.2+
- **mbedtls**: AES-256-GCM, SHA-256 (built-in)
- **lwIP**: TCP/IP stack (built-in)
- **FreeRTOS**: RTOS (built-in)
- **wireguard-esp32**: WireGuard tunnel library (NOT YET INTEGRATED)
## API Contract (Shared with Server)
```
POST /api/v1/provision → { token, hwid } → { encrypted_config }
POST /api/v1/heartbeat → { device_id, tunnel_up, last_handshake } → { config sync }
```
See `docs/API_COMPAT.md` for full specification.
## Related
- [Server Core API](../../server-core/docs/)
- [Device Agent (Go)](../../apps/device-agent/)
- [Android Agent](../../apps/android-agent/)
## License
Proprietary - NexusGuard
+335
View File
@@ -0,0 +1,335 @@
# API Compatibility Reference
**Version**: 1.0.0
**Date**: 2026-06-26
This document defines the HTTP API contract between NexusGuard Server Core and Embedded Agent (ESP32).
## Overview
The embedded agent communicates with the server via two HTTP endpoints:
1. **Provisioning**: `POST /api/v1/provision` — Initial key exchange
2. **Heartbeat**: `POST /api/v1/heartbeat` — Periodic status + config sync
## 1. Provisioning
### Request
```
POST /api/v1/provision
Content-Type: application/json
```
```json
{
"token": "string (required)",
"hwid": "string (required)"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `token` | string | Yes | Registration token (single-use) |
| `hwid` | string | Yes | Hardware ID (ESP32 MAC or serial) |
### Response (Success)
```
HTTP/1.1 200 OK
Content-Type: application/json
```
```json
{
"encrypted_config": "base64-encoded-bytes"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `encrypted_config` | string | AES-256-GCM encrypted ConfigPayload |
### Response (Error)
```json
{
"error": "string"
}
```
| Status | Error | Description |
|--------|-------|-------------|
| 400 | `"invalid token format"` | Token is not valid UUID |
| 400 | `"token already used"` | Token was already consumed |
| 400 | `"invalid hardware id"` | HWID is empty or invalid |
| 403 | `"token expired"` | Token has expired (24h TTL) |
| 500 | `"provisioning failed"` | Server internal error |
### Decrypted ConfigPayload
After decrypting `encrypted_config`, the agent receives:
```json
{
"device_id": "string (UUID)",
"private_key": "string (hex, 64 chars)",
"preshared_key": "string (hex, 64 chars)",
"internal_ip": "string (e.g. 10.172.21.2)",
"server_pub": "string (hex, 64 chars)",
"endpoint": "string (e.g. italy-twenty.gl.at.ply.gg:59750)",
"dns": "string (e.g. 1.1.1.1)",
"allowed_ips": "string (e.g. 0.0.0.0/0)",
"server_wg_ip": "string (e.g. 10.172.21.1)"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `device_id` | string | Device UUID (use in heartbeat) |
| `private_key` | string | WireGuard private key (hex) |
| `preshared_key` | string | Pre-shared key (hex) |
| `internal_ip` | string | Device IP address |
| `server_pub` | string | Server WireGuard public key (hex) |
| `endpoint` | string | Server WireGuard endpoint |
| `dns` | string | DNS server IP |
| `allowed_ips` | string | Allowed IPs (e.g. "0.0.0.0/0" for full tunnel) |
| `server_wg_ip` | string | Server WireGuard IP |
## 2. Heartbeat
### Request
```
POST /api/v1/heartbeat
Content-Type: application/json
```
```json
{
"device_id": "string (required)",
"status": "string (optional)",
"state": "string (optional)",
"tunnel_up": "boolean (optional)",
"last_handshake": "string (optional, ISO 8601)"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `device_id` | string | Yes | Device UUID |
| `status` | string | No | "connected", "recovering", "stopped" |
| `state` | string | No | Free-form state description |
| `tunnel_up` | boolean | No | WireGuard tunnel status |
| `last_handshake` | string | No | ISO 8601 timestamp of last handshake |
### Response (Success)
```
HTTP/1.1 200 OK
Content-Type: application/json
```
```json
{
"device_id": "string",
"status": "ok",
"internal_ip": "string",
"private_key": "string (hex)",
"preshared_key": "string (hex)",
"server_pub": "string (hex)",
"endpoint": "string",
"server_wg_ip": "string",
"dns": "string",
"allowed_ips": "string",
"forwards_hash": "string"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `device_id` | string | Device UUID |
| `status` | string | "ok" or error |
| `internal_ip` | string | Device IP (may change) |
| `private_key` | string | WireGuard private key (may rotate) |
| `preshared_key` | string | Pre-shared key (may rotate) |
| `server_pub` | string | Server public key |
| `endpoint` | string | Server endpoint |
| `server_wg_ip` | string | Server WireGuard IP |
| `dns` | string | DNS server |
| `allowed_ips` | string | Allowed IPs |
| `forwards_hash` | string | Hash of port forwards (for change detection) |
### Response (Error)
```json
{
"error": "string"
}
```
| Status | Error | Description |
|--------|-------|-------------|
| 400 | `"invalid device id"` | Device ID is not valid UUID |
| 403 | `"device is suspended"` | Device is suspended by admin |
| 500 | `"heartbeat failed"` | Server internal error |
## 3. Crypto Protocol
### Key Derivation
```
key = SHA256(hwid + salt)
```
- `hwid`: Hardware ID (ESP32 MAC or serial)
- `salt`: Server's `SERVER_SALT` environment variable
- Output: 32-byte AES key
### Encryption (Server Side)
```
plaintext = ConfigPayload JSON
nonce = random 12 bytes
ciphertext = AES-256-GCM-Encrypt(key, nonce, plaintext)
encrypted_config = nonce + ciphertext
```
### Decryption (Agent Side)
```
encrypted_config = response.encrypted_config (base64 decoded)
nonce = encrypted_config[0:12]
ciphertext = encrypted_config[12:]
plaintext = AES-256-GCM-Decrypt(key, nonce, ciphertext)
ConfigPayload = JSON.parse(plaintext)
```
### Implementation Notes
- Use `mbedtls_sha256()` for key derivation
- Use `mbedtls_gcm_*()` for AES-256-GCM
- Nonce size: 12 bytes (fixed for GCM)
- Tag size: 16 bytes (default for GCM)
## 4. Config Change Detection
### Heartbeat Hash Comparison
The server returns `forwards_hash` in heartbeat response. Agent should:
1. Store last received `forwards_hash` in NVS
2. Compare with current hash
3. If different, trigger config reload
```c
// Pseudo-code
char last_hash[64];
nvs_get_str(nvs_handle, "config_hash", last_hash, sizeof(last_hash));
if (strcmp(last_hash, response.forwards_hash) != 0) {
// Config changed, rebuild tunnel
tunnel_rebuild(&config);
nvs_set_str(nvs_handle, "config_hash", response.forwards_hash);
}
```
## 5. Versioning
### Current Version
- API Version: v1 (implicit, no version in URL)
- Agent Version: 1.0.0
### Compatibility Policy
- **Breaking Changes**: Major version bump (v2)
- **New Fields**: Added without version bump (agent ignores unknown fields)
- **Deprecation**: 6-month notice before removal
### Agent Behavior
- Agent ignores unknown fields in server response
- Agent sends only required fields in request
- Agent handles missing optional fields gracefully
## 6. Examples
### Provisioning Flow
```c
// 1. Get hardware ID
char hwid[32];
get_hardware_id(hwid, sizeof(hwid));
// 2. Send provisioning request
char *response = http_post(server_url, "/api/v1/provision",
"{\"token\":\"%s\",\"hwid\":\"%s\"}", token, hwid);
// 3. Parse encrypted config
cJSON *json = cJSON_Parse(response);
char *encrypted = cJSON_GetObjectItem(json, "encrypted_config")->valuestring;
// 4. Decrypt config
wg_config_t config;
decrypt_config(encrypted, strlen(encrypted), hwid, salt, &config);
// 5. Store in NVS
config_save(&config);
// 6. Apply to WireGuard
tunnel_init(&config);
```
### Heartbeat Flow
```c
// 1. Get tunnel status
bool tunnel_up = tunnel_is_up();
int64_t last_hs = tunnel_get_last_handshake();
// 2. Send heartbeat
char *response = http_post(server_url, "/api/v1/heartbeat",
"{\"device_id\":\"%s\",\"tunnel_up\":%s,\"last_handshake\":\"%s\"}",
config.device_id, tunnel_up ? "true" : "false",
format_timestamp(last_hs));
// 3. Check for config changes
cJSON *json = cJSON_Parse(response);
const char *new_hash = cJSON_GetObjectItem(json, "forwards_hash")->valuestring;
if (strcmp(config.config_hash, new_hash) != 0) {
// Config changed, reload
load_config_from_response(json, &config);
tunnel_rebuild(&config);
config_save(&config);
}
```
## 7. Error Handling
### HTTP Retries
```c
#define MAX_RETRIES 3
#define RETRY_DELAY_MS 1000
for (int i = 0; i < MAX_RETRIES; i++) {
esp_err_t err = http_post(...);
if (err == ESP_OK) break;
vTaskDelay(RETRY_DELAY_MS * (i + 1) / portTICK_PERIOD_MS);
}
```
### Timeout Settings
- Connect timeout: 10 seconds
- Read timeout: 30 seconds
- Write timeout: 10 seconds
## 8. Security Notes
- All communication over HTTPS (TLS 1.2+)
- Registration token is single-use (consumed on first provisioning)
- Device can re-provision by obtaining new token
- Keys never logged or transmitted in plaintext
+387
View File
@@ -0,0 +1,387 @@
# ESP32 Agent — Module Architecture
**Status**: ARCHITECTURE ONLY
**Last Updated**: 2026-07-06
## Overview
This document defines the module architecture for the ESP32-based WireGuard agent. The agent runs on FreeRTOS + ESP-IDF and connects to NexusGuard Server Core via HTTP.
## Task Architecture
```
FreeRTOS Tasks
├── main_task # Initialization, event loop
├── wifi_task # WiFi STA management (priority: 5)
├── http_task # HTTP client (provisioning, heartbeat) (priority: 4)
├── wg_task # WireGuard tunnel management (priority: 3)
└── led_task # Status LED indication (priority: 1)
```
## State Machine
```
┌─────────────┐
│ BOOT │
└──────┬──────┘
┌─────────────┐
│ WIFI_INIT │ ◄──────────────────────────────┐
└──────┬──────┘ │
│ connected │
▼ │
┌─────────────┐ fail ┌─────────────┐│
│ PROVISION │───────────────►│ WIFI_RETRY ││
└──────┬──────┘ └──────┬──────┘│
│ success │ │
▼ └───────┘
┌─────────────┐
│ TUNNEL_UP │
└──────┬──────┘
┌─────────────┐
│ HEARTBEAT │ ◄─── 30s interval
└──────┬──────┘
│ config_changed
┌─────────────┐
│ TUNNEL_REBUILD │
└──────┬──────┘
└──► HEARTBEAT
```
## Module Design
### 1. main.c
**Responsibilities**:
- Hardware initialization (NVS, WiFi, GPIO)
- Task creation and event loop
- Signal handling (Ctrl+C graceful shutdown)
**Key Functions**:
```c
void app_main(void); // Entry point
void shutdown_handler(void); // Graceful shutdown
```
### 2. provision.c
**Responsibilities**:
- HTTP POST to `/api/v1/provision`
- Parse encrypted config response
- Decrypt config via crypto.c
- Store config in NVS
**Key Functions**:
```c
esp_err_t provision_device(const char *server_url, const char *token,
const char *hwid, wg_config_t *config);
esp_err_t decrypt_config(const uint8_t *encrypted, size_t len,
const char *hwid, const char *salt,
wg_config_t *config);
```
**HTTP Request**:
```json
POST /api/v1/provision
Content-Type: application/json
{
"token": "registration-token",
"hwid": "esp32-hardware-id"
}
```
**HTTP Response**:
```json
{
"encrypted_config": "base64-encoded-bytes"
}
```
### 3. heartbeat.c
**Responsibilities**:
- HTTP POST to `/api/v1/heartbeat` every 30s
- Send device status + tunnel state
- Receive config sync (detect changes)
- Trigger tunnel rebuild if config changed
**Key Functions**:
```c
esp_err_t heartbeat_send(const wg_config_t *config,
const char *status, bool tunnel_up,
int64_t last_handshake);
bool heartbeat_config_changed(const char *old_hash, const char *new_hash);
```
**HTTP Request**:
```json
POST /api/v1/heartbeat
Content-Type: application/json
{
"device_id": "uuid",
"status": "connected",
"tunnel_up": true,
"last_handshake": "2026-06-26T09:00:00Z"
}
```
**HTTP Response**:
```json
{
"device_id": "uuid",
"status": "ok",
"internal_ip": "10.172.21.2",
"private_key": "hex",
"preshared_key": "hex",
"server_pub": "hex",
"endpoint": "italy-twenty.gl.at.ply.gg:59750",
"dns": "1.1.1.1",
"allowed_ips": "0.0.0.0/0",
"server_wg_ip": "10.172.21.1",
"forwards_hash": "hash-string"
}
```
### 4. tunnel.c
**Responsibilities**:
- Initialize WireGuard tunnel via lwIP
- Apply config (keys, endpoint, allowed IPs)
- Monitor tunnel state (handshake time)
- Rebuild tunnel on config change
**Key Functions**:
```c
esp_err_t tunnel_init(const wg_config_t *config);
esp_err_t tunnel_apply_config(const wg_config_t *config);
esp_err_t tunnel_rebuild(const wg_config_t *config);
int64_t tunnel_get_last_handshake(void);
bool tunnel_is_up(void);
```
**WireGuard Config Application**:
```c
// Pseudo-code
wg_device wg = {
.private_key = config->private_key,
.listen_port = 0, // ephemeral
};
wg_peer peer = {
.public_key = config->server_pub,
.preshared_key = config->preshared_key,
.endpoint = config->endpoint,
.allowed_ips = config->allowed_ips,
};
wg_set_device(&wg);
wg_add_peer(&wg, &peer);
wg_set_peer_allowed_ips(&wg, &peer, config->allowed_ips);
```
### 5. crypto.c
**Responsibilities**:
- Key derivation: SHA256(hwid + salt)
- AES-256-GCM decryption
- Secure memory handling
**Key Functions**:
```c
esp_err_t crypto_derive_key(const char *hwid, const char *salt,
uint8_t key[32]);
esp_err_t crypto_decrypt(const uint8_t *ciphertext, size_t len,
const uint8_t key[32], uint8_t **plaintext,
size_t *plaintext_len);
```
**Algorithm**:
```
Key Derivation:
key = SHA256(hwid + salt) // 32 bytes
Decryption:
nonce = ciphertext[0:12] // first 12 bytes
encrypted = ciphertext[12:] // rest
plaintext = AES-256-GCM-Decrypt(key, nonce, encrypted)
```
### 6. config.c
**Responsibilities**:
- NVS read/write for config fields
- Config validation
- Config versioning
**Key Functions**:
```c
esp_err_t config_save(const wg_config_t *config);
esp_err_t config_load(wg_config_t *config);
esp_err_t config_clear(void);
bool config_is_valid(const wg_config_t *config);
```
**NVS Keys**:
| Key | Max Size | Description |
|-----|----------|-------------|
| `device_id` | 37 | UUID string |
| `private_key` | 64 | WG private key hex |
| `preshared_key` | 64 | PSK hex |
| `internal_ip` | 18 | IP address |
| `server_pub` | 64 | Server public key hex |
| `endpoint` | 128 | Server endpoint |
| `dns` | 64 | DNS server |
| `allowed_ips` | 128 | Allowed IPs |
| `config_hash` | 64 | Last config hash |
### 7. wifi.c
**Responsibilities**:
- WiFi STA initialization
- Connection management
- Reconnection handling
**Key Functions**:
```c
esp_err_t wifi_init_sta(const char *ssid, const char *password);
esp_err_t wifi_connect(void);
esp_err_t wifi_disconnect(void);
bool wifi_is_connected(void);
```
## Data Structures
### wg_config_t
```c
typedef struct {
char device_id[37]; // UUID
char private_key[64]; // WG private key hex
char preshared_key[64]; // PSK hex
char internal_ip[18]; // e.g. "10.172.21.2"
char server_pub[64]; // Server WG public key hex
char endpoint[128]; // e.g. "italy-twenty.gl.at.ply.gg:59750"
char dns[64]; // DNS server
char allowed_ips[128]; // e.g. "0.0.0.0/0"
char server_wg_ip[18]; // e.g. "10.172.21.1"
char config_hash[64]; // Last config hash
} wg_config_t;
```
### device_state_t
```c
typedef enum {
STATE_BOOT,
STATE_WIFI_INIT,
STATE_WIFI_CONNECTED,
STATE_PROVISIONING,
STATE_PROVISIONED,
STATE_TUNNEL_UP,
STATE_TUNNEL_DOWN,
STATE_HEARTBEAT,
STATE_ERROR
} device_state_t;
```
## Error Handling
### Error Codes
```c
typedef enum {
ERR_OK = 0,
ERR_WIFI_CONNECT = -1,
ERR_HTTP_PROVISION = -2,
ERR_HTTP_HEARTBEAT = -3,
ERR_CRYPTO_DECRYPT = -4,
ERR_CONFIG_INVALID = -5,
ERR_TUNNEL_INIT = -6,
ERR_TUNNEL_APPLY = -7,
ERR_NVS_READ = -8,
ERR_NVS_WRITE = -9,
} agent_error_t;
```
### Recovery Strategy
| Error | Recovery |
|-------|----------|
| WiFi connect fail | Retry 3x, then reboot |
| HTTP provision fail | Retry 3x, then reboot |
| HTTP heartbeat fail | Log only, continue |
| Crypto decrypt fail | Clear config, re-provision |
| Tunnel init fail | Retry 3x, then reboot |
| NVS read fail | Use defaults, re-provision |
## Implementation Status
### ✅ Implemented (Real Code)
| Module | File | Status |
|--------|------|--------|
| Provisioning | `provision.c` | HTTP POST working |
| Heartbeat | `heartbeat.c` | HTTP loop working |
| Crypto | `crypto.c` | mbedtls AES-256-GCM |
| Config | `config.c` | NVS read/write |
| WiFi | `wifi.c` | STA connection |
### ❌ STUB (Needs Implementation)
| Module | File | What's Missing |
|--------|------|----------------|
| Tunnel | `tunnel.c` | `wireguard-esp32` component integration |
| Heartbeat | `heartbeat.c` | Config sync parsing from response |
### TODO: tunnel.c Integration
When integrating `wireguard-esp32` component:
```c
// 1. Parse private_key hex to binary
// 2. Parse server_pub hex to binary
// 3. Parse preshared_key hex to binary
// 4. Create WireGuard device with private_key
// 5. Add peer with server_pub, preshared_key, endpoint
// 6. Set peer allowed_ips
// 7. Start WireGuard tunnel
```
### TODO: heartbeat.c Config Sync
When parsing heartbeat response:
```c
// 1. Parse response JSON
// 2. Extract config_hash
// 3. Compare with stored config_hash
// 4. If different → parse new config fields
// 5. Save to NVS
// 6. Call tunnel_rebuild()
```
## Future Enhancements
### Phase 2
- OTA firmware updates
- Custom CA certificate support
- Power optimization (deep sleep)
- Multiple server support
### Phase 3
- Zephyr RTOS port
- ESP32-S2 support
- Ethernet (SPI) support
- Bluetooth provisioning
## References
- [ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/)
- [WireGuard Protocol](https://www.wireguard.com/protocol/)
- [lwIP Documentation](https://www.nongnu.org/lwip/)
- [mbedtls Documentation](https://mbed-tls.readthedocs.io/)
+582
View File
@@ -0,0 +1,582 @@
# Technical Design Document
**Version**: 1.0.0
**Date**: 2026-06-26
**Status**: Draft
## 1. Executive Summary
NexusGuard Embedded Agent is an ESP32-based WireGuard client designed for IoT and edge devices. It provides zero-trust VPN tunneling with auto-provisioning and heartbeat monitoring, connecting to the existing NexusGuard Server Core infrastructure.
## 2. Goals & Non-Goals
### Goals
- ESP32-based WireGuard tunnel (LW840X compatible)
- Auto-provisioning via HTTP (same API as Go agent)
- Heartbeat monitoring with config sync
- NVS-based config persistence (encrypted)
- FreeRTOS task-based architecture
- Low power consumption (WiFi sleep modes)
- **Independent build** — no dependency on main NexusGuard repo
- **Server discovery** — configurable server URL + registration token
### Non-Goals
- Port forwarding (userspace agent only)
- gRPC support (too heavy for ESP32)
- Zephyr RTOS support (ESP-IDF uses FreeRTOS)
- Multi-peer support (single tunnel only)
- Kernel WireGuard (lwIP userspace only)
## 3. Architecture
### 3.1 System Architecture
```
┌─────────────────────────────────────────────────────────┐
│ ESP32 (FreeRTOS) │
├─────────────────────────────────────────────────────────┤
│ Application Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ provision│ │ heartbeat│ │ tunnel │ │ config │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ crypto.c │ │
│ │ AES-256-GCM decrypt (mbedtls) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Network Stack (lwIP) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ WireGuard (lwIP socket API) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Hardware Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ WiFi │ │ NVS │ │ UART │ │ GPIO │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Task Architecture
```
FreeRTOS Tasks
├── main_task # Initialization, event loop
├── wifi_task # WiFi STA management (priority: 5)
├── http_task # HTTP client (provisioning, heartbeat) (priority: 4)
├── wg_task # WireGuard tunnel management (priority: 3)
└── led_task # Status LED indication (priority: 1)
```
### 3.3 State Machine
```
┌─────────────┐
│ BOOT │
└──────┬──────┘
┌─────────────┐
│ WIFI_INIT │ ◄──────────────────────────────┐
└──────┬──────┘ │
│ connected │
▼ │
┌─────────────┐ fail ┌─────────────┐│
│ PROVISION │───────────────►│ WIFI_RETRY ││
└──────┬──────┘ └──────┬──────┘│
│ success │ │
▼ └───────┘
┌─────────────┐
│ TUNNEL_UP │
└──────┬──────┘
┌─────────────┐
│ HEARTBEAT │ ◄─── 30s interval
└──────┬──────┘
│ config_changed
┌─────────────┐
│ TUNNEL_REBUILD │
└──────┬──────┘
└──► HEARTBEAT
```
## 4. Server Discovery & Provisioning
### 4.1 Overview
The embedded agent connects to NexusGuard Server Core via HTTP. The agent must be configured with:
- **Server URL**: The API endpoint (e.g., `https://api-nexus.datadunia.com`)
- **Registration Token**: Single-use token for initial provisioning
- **Server Salt**: For AES-256-GCM key derivation (shared secret)
### 4.2 Provisioning Flow
```
┌─────────────────────────────────────────────────────────────────┐
│ PROVISIONING FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Device boots │
│ │ │
│ ▼ │
│ 2. Check NVS for saved config │
│ │ │
│ ├─► Config exists ──► Load config ──► Skip to TUNNEL │
│ │ │
│ └─► No config ──► Continue to provisioning │
│ │
│ 3. Connect to WiFi │
│ │ │
│ ▼ │
│ 4. HTTP POST /api/v1/provision │
│ │ Request: { "token": "...", "hwid": "..." } │
│ │ │
│ ▼ │
│ 5. Server validates token, generates WireGuard keys │
│ │ │
│ ▼ │
│ 6. Server encrypts config with AES-256-GCM │
│ │ Key = SHA256(hwid + salt) │
│ │ │
│ ▼ │
│ 7. Server responds with encrypted_config │
│ │ │
│ ▼ │
│ 8. Agent decrypts config using mbedtls │
│ │ │
│ ▼ │
│ 9. Store config in NVS │
│ │ │
│ ▼ │
│ 10. Apply WireGuard tunnel │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 4.3 Server Configuration Sources
The embedded agent can obtain server configuration from:
| Source | Priority | Use Case |
|--------|----------|----------|
| NVS (saved config) | 1 | After initial provisioning |
| sdkconfig (menuconfig) | 2 | Build-time configuration |
| SmartConfig / BLE | 3 | WiFi provisioning (future) |
| QR Code | 4 | Manual provisioning (future) |
### 4.4 Build-Time Configuration
Configure server URL and token via `idf.py menuconfig`:
```
Component config → NexusGuard Agent
├── Server URL (NEXUS_SERVER_URL)
├── Registration Token (NEXUS_REG_TOKEN)
├── Server Salt (NEXUS_SERVER_SALT)
└── WiFi SSID (NEXUS_WIFI_SSID)
└── WiFi Password (NEXUS_WIFI_PASSWORD)
```
### 4.5 Runtime Configuration (NVS)
After first provisioning, config is stored in NVS:
| Key | Description |
|-----|-------------|
| `server_url` | Server API URL |
| `device_id` | Assigned device UUID |
| `private_key` | WireGuard private key |
| `preshared_key` | Pre-shared key |
| `internal_ip` | Device IP address |
| `server_pub` | Server public key |
| `endpoint` | Server WireGuard endpoint |
| `dns` | DNS server |
| `allowed_ips` | Allowed IPs |
| `server_wg_ip` | Server WireGuard IP |
| `config_hash` | Last config hash |
### 4.6 Server API Contract
The embedded agent uses the **same HTTP API** as the Go device-agent:
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/v1/provision` | POST | Initial key exchange |
| `/api/v1/heartbeat` | POST | Periodic status + config sync |
**Key**: The API contract is stable and documented in `docs/API_COMPAT.md`. The server does not need to know whether the client is Go agent or ESP32 agent — it treats them identically.
### 4.7 Independence from Main Repo
The embedded agent is **fully independent**:
```
nexus-agent-embedded/ # Standalone repo
├── main/ # C source code
├── components/ # ESP-IDF components
├── docs/ # Documentation
├── CMakeLists.txt # Build system
└── sdkconfig.defaults # Configuration
```
**No dependency on**:
- `server-core` (Go backend)
- `device-agent` (Go agent)
- `dashboard-ui` (Vue frontend)
**Build anywhere** with ESP-IDF toolchain — no Go, no Node.js, no Docker.
## 5. Module Design
### 4.1 main.c
**Responsibilities**:
- Hardware initialization (NVS, WiFi, GPIO)
- Task creation and event loop
- Signal handling (Ctrl+C graceful shutdown)
**Key Functions**:
```c
void app_main(void); // Entry point
void shutdown_handler(void); // Graceful shutdown
```
### 4.2 provision.c
**Responsibilities**:
- HTTP POST to `/api/v1/provision`
- Parse encrypted config response
- Decrypt config via crypto.c
- Store config in NVS
**Key Functions**:
```c
esp_err_t provision_device(const char *server_url, const char *token,
const char *hwid, wg_config_t *config);
esp_err_t decrypt_config(const uint8_t *encrypted, size_t len,
const char *hwid, const char *salt,
wg_config_t *config);
```
**HTTP Request**:
```json
POST /api/v1/provision
Content-Type: application/json
{
"token": "registration-token",
"hwid": "esp32-hardware-id"
}
```
**HTTP Response**:
```json
{
"encrypted_config": "base64-encoded-bytes"
}
```
### 4.3 heartbeat.c
**Responsibilities**:
- HTTP POST to `/api/v1/heartbeat` every 30s
- Send device status + tunnel state
- Receive config sync (detect changes)
- Trigger tunnel rebuild if config changed
**Key Functions**:
```c
esp_err_t heartbeat_send(const wg_config_t *config,
const char *status, bool tunnel_up,
int64_t last_handshake);
bool heartbeat_config_changed(const char *old_hash, const char *new_hash);
```
**HTTP Request**:
```json
POST /api/v1/heartbeat
Content-Type: application/json
{
"device_id": "uuid",
"status": "connected",
"tunnel_up": true,
"last_handshake": "2026-06-26T09:00:00Z"
}
```
**HTTP Response**:
```json
{
"device_id": "uuid",
"status": "ok",
"internal_ip": "10.172.21.2",
"private_key": "hex",
"preshared_key": "hex",
"server_pub": "hex",
"endpoint": "italy-twenty.gl.at.ply.gg:59750",
"dns": "1.1.1.1",
"allowed_ips": "0.0.0.0/0",
"server_wg_ip": "10.172.21.1",
"forwards_hash": "hash-string"
}
```
### 4.4 tunnel.c
**Responsibilities**:
- Initialize WireGuard tunnel via lwIP
- Apply config (keys, endpoint, allowed IPs)
- Monitor tunnel state (handshake time)
- Rebuild tunnel on config change
**Key Functions**:
```c
esp_err_t tunnel_init(const wg_config_t *config);
esp_err_t tunnel_apply_config(const wg_config_t *config);
esp_err_t tunnel_rebuild(const wg_config_t *config);
int64_t tunnel_get_last_handshake(void);
bool tunnel_is_up(void);
```
**WireGuard Config Application**:
```c
// Pseudo-code
wg_device wg = {
.private_key = config->private_key,
.listen_port = 0, // ephemeral
};
wg_peer peer = {
.public_key = config->server_pub,
.preshared_key = config->preshared_key,
.endpoint = config->endpoint,
.allowed_ips = config->allowed_ips,
};
wg_set_device(&wg);
wg_add_peer(&wg, &peer);
wg_set_peer_allowed_ips(&wg, &peer, config->allowed_ips);
```
### 4.5 crypto.c
**Responsibilities**:
- Key derivation: SHA256(hwid + salt)
- AES-256-GCM decryption
- Secure memory handling
**Key Functions**:
```c
esp_err_t crypto_derive_key(const char *hwid, const char *salt,
uint8_t key[32]);
esp_err_t crypto_decrypt(const uint8_t *ciphertext, size_t len,
const uint8_t key[32], uint8_t **plaintext,
size_t *plaintext_len);
```
**Algorithm**:
```
Key Derivation:
key = SHA256(hwid + salt) // 32 bytes
Decryption:
nonce = ciphertext[0:12] // first 12 bytes
encrypted = ciphertext[12:] // rest
plaintext = AES-256-GCM-Decrypt(key, nonce, encrypted)
```
### 4.6 config.c
**Responsibilities**:
- NVS read/write for config fields
- Config validation
- Config versioning
**Key Functions**:
```c
esp_err_t config_save(const wg_config_t *config);
esp_err_t config_load(wg_config_t *config);
esp_err_t config_clear(void);
bool config_is_valid(const wg_config_t *config);
```
**NVS Keys**:
| Key | Max Size | Description |
|-----|----------|-------------|
| `device_id` | 37 | UUID string |
| `private_key` | 64 | WG private key hex |
| `preshared_key` | 64 | PSK hex |
| `internal_ip` | 18 | IP address |
| `server_pub` | 64 | Server public key hex |
| `endpoint` | 128 | Server endpoint |
| `dns` | 64 | DNS server |
| `allowed_ips` | 128 | Allowed IPs |
| `config_hash` | 64 | Last config hash |
### 4.7 wifi.c
**Responsibilities**:
- WiFi STA initialization
- Connection management
- Reconnection handling
**Key Functions**:
```c
esp_err_t wifi_init_sta(const char *ssid, const char *password);
esp_err_t wifi_connect(void);
esp_err_t wifi_disconnect(void);
bool wifi_is_connected(void);
```
## 5. Data Structures
### 5.1 wg_config_t
```c
typedef struct {
char device_id[37]; // UUID
char private_key[64]; // WG private key hex
char preshared_key[64]; // PSK hex
char internal_ip[18]; // e.g. "10.172.21.2"
char server_pub[64]; // Server WG public key hex
char endpoint[128]; // e.g. "italy-twenty.gl.at.ply.gg:59750"
char dns[64]; // DNS server
char allowed_ips[128]; // e.g. "0.0.0.0/0"
char server_wg_ip[18]; // e.g. "10.172.21.1"
char config_hash[64]; // Last config hash
} wg_config_t;
```
### 5.2 device_state_t
```c
typedef enum {
STATE_BOOT,
STATE_WIFI_INIT,
STATE_WIFI_CONNECTED,
STATE_PROVISIONING,
STATE_PROVISIONED,
STATE_TUNNEL_UP,
STATE_TUNNEL_DOWN,
STATE_HEARTBEAT,
STATE_ERROR
} device_state_t;
```
## 6. Error Handling
### 6.1 Error Codes
```c
typedef enum {
ERR_OK = 0,
ERR_WIFI_CONNECT = -1,
ERR_HTTP_PROVISION = -2,
ERR_HTTP_HEARTBEAT = -3,
ERR_CRYPTO_DECRYPT = -4,
ERR_CONFIG_INVALID = -5,
ERR_TUNNEL_INIT = -6,
ERR_TUNNEL_APPLY = -7,
ERR_NVS_READ = -8,
ERR_NVS_WRITE = -9,
} agent_error_t;
```
### 6.2 Recovery Strategy
| Error | Recovery |
|-------|----------|
| WiFi connect fail | Retry 3x, then reboot |
| HTTP provision fail | Retry 3x, then reboot |
| HTTP heartbeat fail | Log only, continue |
| Crypto decrypt fail | Clear config, re-provision |
| Tunnel init fail | Retry 3x, then reboot |
| NVS read fail | Use defaults, re-provision |
## 7. Power Management
### 7.1 WiFi Sleep
```c
// Enable WiFi sleep when tunnel is up
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
// Disable sleep during provisioning/heartbeat
esp_wifi_set_ps(WIFI_PS_NONE);
```
### 7.2 Light Sleep
```c
// Enter light sleep between heartbeats (30s)
esp_light_sleep_start();
// Wake on: WiFi event, GPIO interrupt, timer
```
## 8. Security Considerations
### 8.1 Key Storage
- WireGuard keys stored in NVS (flash, encrypted by NVS encryption)
- Never log keys or tokens
- Clear memory after use: `memset_s(key, 0, sizeof(key))`
### 8.2 TLS
- Server communication over HTTPS (TLS 1.2+)
- Skip certificate verification for internal network (configurable)
- Future: Support custom CA certificate
### 8.3 Hardware ID
- Derived from ESP32 eFuse MAC or custom serial
- Used for key derivation (HWID + salt)
- Never transmitted in plaintext
## 9. Testing Strategy
### 9.1 Unit Tests
- Crypto: Decrypt known ciphertext with known key
- Config: NVS read/write cycle
- Heartbeat: Parse server response
### 9.2 Integration Tests
- Provision → Tunnel → Heartbeat cycle
- Config change detection
- Tunnel rebuild on config change
### 9.3 Hardware Tests
- ESP32-S3 DevKit flashing
- WiFi connection stability
- WireGuard handshake verification
- Power consumption measurement
## 10. Future Enhancements
### 10.1 Phase 2
- OTA firmware updates
- Custom CA certificate support
- Power optimization (deep sleep)
- Multiple server support
### 10.2 Phase 3
- Zephyr RTOS port
- ESP32-S2 support
- Ethernet (SPI) support
- Bluetooth provisioning
## 11. References
- [ESP-IDF Documentation](https://docs.espressif.com/projects/esp-idf/)
- [WireGuard Protocol](https://www.wireguard.com/protocol/)
- [lwIP Documentation](https://www.nongnu.org/lwip/)
- [mbedtls Documentation](https://mbed-tls.readthedocs.io/)
- [NexusGuard Server API](../../server-core/docs/)
+330
View File
@@ -0,0 +1,330 @@
# Hardware Reference
**Version**: 1.0.0
**Date**: 2026-06-26
## 1. Target Hardware
### 1.1 LW840X Module
The LW840X is an ESP32-based WiFi module designed for IoT applications.
| Specification | Value |
|---------------|-------|
| MCU | ESP32 (Xtensa LX7 dual-core) |
| Clock | 240 MHz |
| RAM | 520 KB SRAM |
| Flash | 4 MB (external) |
| WiFi | 802.11 b/g/n (2.4 GHz) |
| Bluetooth | None (LW840X) |
| GPIO | 20+ (depends on module) |
| ADC | 18 channels (12-bit) |
| Operating Temp | -40°C to +85°C |
| Voltage | 3.0V to 3.6V |
### 1.2 Pin Mapping
```
LW840X Module Pinout
┌─────────────────────────────────────┐
│ │
│ 3V3 ─┐ │
│ GND ─┤ │
│ EN ─┤ │
│ IO0 ─┤ Boot/Flash mode │
│ IO1 ─┤ TX0 (UART0) │
│ IO3 ─┤ RX0 (UART0) │
│ IO4 ─┤ Status LED │
│ IO5 ─┤ (reserved) │
│ IO12 ─┤ (reserved) │
│ IO13 ─┤ (reserved) │
│ IO14 ─┤ (reserved) │
│ IO15 ─┤ (reserved) │
│ IO16 ─┤ (reserved) │
│ IO17 ─┤ (reserved) │
│ IO18 ─┤ (reserved) │
│ IO19 ─┤ (reserved) │
│ IO21 ─┤ (reserved) │
│ IO22 ─┤ (reserved) │
│ IO23 ─┤ (reserved) │
│ IO25 ─┤ (reserved) │
│ IO26 ─┤ (reserved) │
│ IO27 ─┤ (reserved) │
│ IO32 ─┤ (reserved) │
│ IO33 ─┤ (reserved) │
│ IO34 ─┤ (reserved) │
│ IO35 ─┤ (reserved) │
│ │
└─────────────────────────────────────┘
```
### 1.3 Minimal Circuit
```
Power Supply:
3.3V ──── LW840X 3V3
GND ──── LW840X GND
Decoupling:
100nF ceramic capacitor between 3V3 and GND (close to module)
Boot/Flash:
IO0 ──── 10K pull-up to 3V3 (normal boot)
IO0 ──── GND (flash mode)
Status LED:
IO4 ──── 220Ω ──── LED ──── GND
UART (for debugging):
IO1 (TX0) ──── USB-UART RX
IO3 (RX0) ──── USB-UART TX
GND ──── USB-UART GND
```
## 2. Power Requirements
### 2.1 Current Consumption
| Mode | Current | Notes |
|------|---------|-------|
| Active (WiFi TX) | 130-170 mA | Transmitting data |
| Active (WiFi RX) | 80-100 mA | Receiving data |
| Modem Sleep | 15-20 mA | WiFi connected, low duty cycle |
| Light Sleep | 0.8-1.5 mA | CPU paused, WiFi wake |
| Deep Sleep | 5-10 µA | RTC only, wake on GPIO |
| Off | 0 µA | No power |
### 2.2 Power Supply Design
```
Recommended:
Input: 5V USB or 12V DC
Regulator: AMS1117-3.3 or similar LDO
Capacity: 500mA minimum
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 5V USB │────▶│ LDO │────▶│ LW840X │
│ │ │ 3.3V │ │ │
└─────────┘ └─────────┘ └─────────┘
GND
```
### 2.3 Battery Operation
For battery-powered applications:
| Battery | Capacity | Runtime (Active) | Runtime (Sleep) |
|---------|----------|------------------|-----------------|
| CR2032 | 225 mAh | ~1.5 hours | ~2 years |
| 18650 | 3400 mAh | ~20 hours | ~30 years |
| LiPo 1000mAh | 1000 mAh | ~6 hours | ~10 years |
**Note**: Deep sleep with periodic wake (e.g., every 5 minutes) is recommended for battery operation.
## 3. Antenna Options
### 3.1 PCB Trace Antenna
- **Pros**: Low cost, compact
- **Cons**: Lower gain, sensitive to placement
- **Range**: 10-30m (indoor)
- **Use Case**: Short-range, cost-sensitive
### 3.2 U.FL Connector
- **Pros**: Higher gain, flexible placement
- **Cons**: Additional cost, larger size
- **Range**: 50-100m (indoor)
- **Use Case**: Longer range, industrial
### 3.3 External Antenna
- **Pros**: Best performance, directional options
- **Cons**: Highest cost, largest size
- **Range**: 100m+ (outdoor)
- **Use Case**: Outdoor, long-range
## 4. Development Board
### 4.1 ESP32-DevKitC
Recommended for development and prototyping.
| Feature | Specification |
|---------|---------------|
| MCU | ESP32-WROOM-32 |
| Flash | 4 MB |
| RAM | 520 KB SRAM |
| WiFi | 802.11 b/g/n |
| Bluetooth | BT 4.2 + BLE |
| USB | Micro-USB (CP2102) |
| GPIO | 38 pins |
| Price | ~$5-10 |
### 4.2 Flashing
```bash
# Install ESP-IDF
# https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/
# Set target
idf.py set-target esp32
# Build
idf.py build
# Flash (auto-detect port)
idf.py -p /dev/ttyUSB0 flash
# Monitor
idf.py -p /dev/ttyUSB0 monitor
```
### 4.3 Pin Connections (DevKit)
```
Status LED:
GPIO4 ──── 220Ω ──── LED ──── GND
Debug UART (optional):
GPIO1 (TX0) ──── USB-UART RX
GPIO3 (RX0) ──── USB-UART TX
GND ──── USB-UART GND
```
## 5. Production Board
### 5.1 Design Guidelines
1. **Power**:
- Use 3.3V LDO with 500mA capacity
- Add 100nF ceramic capacitor close to 3V3 pin
- Add 10µF tantalum capacitor for bulk decoupling
2. **Antenna**:
- Keep antenna area clear of copper pours
- Minimum 10mm clearance around antenna
- Use U.FL connector for external antenna
3. **Layout**:
- Route UART traces away from antenna
- Keep crystal traces short
- Ground plane under module
4. **Enclosure**:
- Use non-metallic enclosure (plastic)
- Ensure antenna is not shielded
- Provide mounting holes
### 5.2 Bill of Materials (BOM)
| Component | Quantity | Package | Notes |
|-----------|----------|---------|-------|
| LW840X | 1 | Module | ESP32-based |
| AMS1117-3.3 | 1 | SOT-223 | 3.3V LDO |
| 100nF | 2 | 0402 | Decoupling |
| 10µF | 1 | 0805 | Bulk cap |
| 220Ω | 1 | 0402 | LED resistor |
| LED | 1 | 0603 | Status indicator |
| U.FL | 1 | SMD | Antenna connector |
| Header | 1 | 2.54mm | Debug UART |
### 5.3 Schematic
```
┌─────────────────┐
│ LW840X │
3.3V ────────────┤ 3V3 GND ├──── GND
│ │
IO4 ──── 220Ω ───┤ IO4 TX0 ├──── UART RX
└── LED ──┤ RX0 ├──── UART TX
GND │ │
│ EN ├──── 10K ──── 3.3V
│ │
│ IO0 ├──── 10K ──── 3.3V
│ │ (flash: GND)
└─────────────────┘
```
## 6. Testing & Validation
### 6.1 Hardware Tests
1. **Power-on Test**:
- Verify 3.3V at module pin
- Check current consumption (~50mA idle)
- Confirm LED blinks on boot
2. **WiFi Test**:
- Scan for WiFi networks
- Connect to test AP
- Measure RSSI at distance
3. **UART Test**:
- Send AT commands (if firmware supports)
- Verify debug output
- Check baud rate (115200)
4. **Flash Test**:
- Write/read NVS data
- Verify flash size
- Test wear leveling
### 6.2 Production Test
1. **Functional Test**:
- Provision with test server
- Verify WireGuard handshake
- Check heartbeat response
2. **Stress Test**:
- Run for 24+ hours
- Monitor memory leaks
- Verify reconnection after WiFi drop
3. **Environmental Test**:
- Operating temperature range
- Humidity resistance
- Vibration resistance
## 7. Troubleshooting
### 7.1 Common Issues
| Symptom | Cause | Solution |
|---------|-------|----------|
| No boot | IO0 held low | Remove flash jumper |
| No WiFi | Antenna issue | Check antenna connection |
| High current | WiFi always on | Use modem sleep |
| Crash on boot | Stack overflow | Increase task stack size |
| Provision fail | Token expired | Get new token from server |
### 7.2 Debug Tools
- **Serial Monitor**: `idf.py monitor`
- **JTAG Debugger**: OpenOCD + GDB
- **Logic Analyzer**: Saleae or similar
- **Power Analyzer**: Monsoon or similar
## 8. Compliance
### 8.1 Certifications
- **FCC**: Required for US market
- **CE**: Required for EU market
- **IC**: Required for Canada market
- **TELEC**: Required for Japan market
### 8.2 Testing
- EMC testing (emissions and immunity)
- SAR testing (if applicable)
- Environmental testing (temperature, humidity)
## 9. References
- [ESP32 Hardware Design Guidelines](https://www.espressif.com/sites/default/files/documentation/esp32_hardware_design_guidelines_en.pdf)
- [ESP32 Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
- [LW840X Module Datasheet](link-to-datasheet)
- [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/)
+58
View File
@@ -0,0 +1,58 @@
# iOS Agent — Architecture Reference
**Status**: ARCHITECTURE ONLY (no code)
**Location**: `reference/ios-agent/` (NOT a submodule)
**Mirror**: Android Agent (`apps/android-agent/`)
## Overview
This directory contains the architecture design for an iOS WireGuard agent that mirrors the Android agent's architecture, UI design, colors, and heartbeat flow.
## Why `reference/` not `apps/`?
If iOS agent is implemented in the future, it will be added as `apps/ios-agent/`. This `reference/` directory stores the architecture docs separately to avoid conflicts.
## Purpose
- Document the iOS agent architecture (mirrors Android)
- Define UI design specs (colors, layout, components)
- API contract (shared with server-core)
- Track implementation status and TODOs
## Architecture Mirror: Android → iOS
| Android Component | iOS Equivalent | Notes |
|-------------------|----------------|-------|
| `AgentService.kt` | `PacketTunnelProvider` | Foreground service → NetworkExtension |
| `TunnelManager.kt` | `WireGuardAdapter` | VPNService → NEPacketTunnelProvider |
| `Heartbeat.kt` | `HeartbeatService` | HTTP loop (same) |
| `Provisioning.kt` | `ProvisioningService` | HTTP provisioning (same) |
| `ConfigStorage.kt` | `KeychainStorage` | SharedPreferences → Keychain |
| `Encryptor.kt` | `CryptoManager` | JCE → CryptoKit |
| `MainActivity.kt` | `ContentView` | Activity → SwiftUI |
| `SettingsActivity.kt` | `SettingsView` | Activity → SwiftUI |
| `BootReceiver.kt` | `BGAppRefreshTask` | Boot broadcast → Background Tasks |
| `LogBuffer.kt` | `LogStore` | In-memory → CoreData |
## Files
| File | Description |
|------|-------------|
| `docs/DESIGN.md` | Technical design + UI specs |
| `docs/ARCHITECTURE.md` | Module architecture |
| `docs/API_COMPAT.md` | Server API contract |
| `docs/UI_DESIGN.md` | UI design specs (colors, layout) |
## Quick Start
When implementing:
1. Create Xcode project: `ios-agent`
2. Add NetworkExtension capability
3. Use `wireguard-apple` library for WireGuard
4. Follow architecture in `docs/ARCHITECTURE.md`
5. Match UI design in `docs/UI_DESIGN.md`
## License
Proprietary - NexusGuard
+365
View File
@@ -0,0 +1,365 @@
# iOS Agent — API Compatibility Reference
**Status**: API CONTRACT
**Mirror**: Android Agent (`apps/android-agent/`)
## Overview
The iOS agent communicates with the server via two HTTP endpoints:
1. **Provisioning**: `POST /api/v1/provision` — Initial key exchange
2. **Heartbeat**: `POST /api/v1/heartbeat` — Periodic status + config sync
## 1. Provisioning
### Request
```
POST /api/v1/provision
Content-Type: application/json
```
```json
{
"token": "string (required)",
"hwid": "string (required)"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `token` | string | Yes | Registration token (single-use) |
| `hwid` | string | Yes | Hardware ID (iOS identifierForVendor) |
### Response (Success)
```
HTTP/1.1 200 OK
Content-Type: application/json
```
```json
{
"encrypted_config": "base64-encoded-bytes"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `encrypted_config` | string | AES-256-GCM encrypted ConfigPayload |
### Response (Error)
```json
{
"error": "string"
}
```
| Status | Error | Description |
|--------|-------|-------------|
| 400 | `"invalid token format"` | Token is not valid UUID |
| 400 | `"token already used"` | Token was already consumed |
| 400 | `"invalid hardware id"` | HWID is empty or invalid |
| 403 | `"token expired"` | Token has expired (24h TTL) |
| 500 | `"provisioning failed"` | Server internal error |
### Decrypted ConfigPayload
After decrypting `encrypted_config`, the agent receives:
```json
{
"device_id": "string (UUID)",
"private_key": "string (hex, 64 chars)",
"preshared_key": "string (hex, 64 chars)",
"internal_ip": "string (e.g. 10.172.21.2)",
"server_pub": "string (hex, 64 chars)",
"endpoint": "string (e.g. italy-twenty.gl.at.ply.gg:59750)",
"dns": "string (e.g. 1.1.1.1)",
"allowed_ips": "string (e.g. 0.0.0.0/0)",
"server_wg_ip": "string (e.g. 10.172.21.1)"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `device_id` | string | Device UUID (use in heartbeat) |
| `private_key` | string | WireGuard private key (hex) |
| `preshared_key` | string | Pre-shared key (hex) |
| `internal_ip` | string | Device IP address |
| `server_pub` | string | Server WireGuard public key (hex) |
| `endpoint` | string | Server WireGuard endpoint |
| `dns` | string | DNS server IP |
| `allowed_ips` | string | Allowed IPs (e.g. "0.0.0.0/0" for full tunnel) |
| `server_wg_ip` | string | Server WireGuard IP |
## 2. Heartbeat
### Request
```
POST /api/v1/heartbeat
Content-Type: application/json
```
```json
{
"device_id": "string (required)",
"status": "string (optional)",
"state": "string (optional)",
"tunnel_up": "boolean (optional)",
"last_handshake": "string (optional, ISO 8601)"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `device_id` | string | Yes | Device UUID |
| `status` | string | No | "connected", "recovering", "stopped" |
| `state` | string | No | Free-form state description |
| `tunnel_up` | boolean | No | WireGuard tunnel status |
| `last_handshake` | string | No | ISO 8601 timestamp of last handshake |
### Response (Success)
```
HTTP/1.1 200 OK
Content-Type: application/json
```
```json
{
"device_id": "string",
"status": "ok",
"internal_ip": "string",
"private_key": "string (hex)",
"preshared_key": "string (hex)",
"server_pub": "string (hex)",
"endpoint": "string",
"server_wg_ip": "string",
"dns": "string",
"allowed_ips": "string",
"forwards_hash": "string"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `device_id` | string | Device UUID |
| `status` | string | "ok" or error |
| `internal_ip` | string | Device IP (may change) |
| `private_key` | string | WireGuard private key (may rotate) |
| `preshared_key` | string | Pre-shared key (may rotate) |
| `server_pub` | string | Server public key |
| `endpoint` | string | Server endpoint |
| `server_wg_ip` | string | Server WireGuard IP |
| `dns` | string | DNS server |
| `allowed_ips` | string | Allowed IPs |
| `forwards_hash` | string | Hash of port forwards (for change detection) |
### Response (Error)
```json
{
"error": "string"
}
```
| Status | Error | Description |
|--------|-------|-------------|
| 400 | `"invalid device id"` | Device ID is not valid UUID |
| 403 | `"device is suspended"` | Device is suspended by admin |
| 500 | `"heartbeat failed"` | Server internal error |
## 3. Crypto Protocol
### Key Derivation
```
key = SHA256(hwid + salt)
```
- `hwid`: Hardware ID (iOS `identifierForVendor`)
- `salt`: Server's `SERVER_SALT` environment variable
- Output: 32-byte AES key
### Encryption (Server Side)
```
plaintext = ConfigPayload JSON
nonce = random 12 bytes
ciphertext = AES-256-GCM-Encrypt(key, nonce, plaintext)
encrypted_config = nonce + ciphertext
```
### Decryption (Agent Side)
```swift
let combined = Data(base64Encoded: encryptedConfig)!
let nonce = combined.prefix(12)
let ciphertext = combined.dropFirst(12)
let sealedBox = try AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext)
let plaintext = try AES.GCM.open(sealedBox, using: key)
let json = try JSONSerialization.jsonObject(with: plaintext)
```
### Implementation Notes
- Use `CryptoKit` for AES-256-GCM
- Use `CryptoKit` for SHA-256 key derivation
- Nonce size: 12 bytes (fixed for GCM)
- Tag size: 16 bytes (default for GCM)
## 4. Config Change Detection
### Heartbeat Hash Comparison
The server returns `forwards_hash` in heartbeat response. Agent should:
1. Store last received `forwards_hash` in UserDefaults
2. Compare with current hash
3. If different, trigger config reload
```swift
let lastHash = UserDefaults.standard.string(forKey: "config_hash") ?? ""
if lastHash != response.forwardsHash {
// Config changed, rebuild tunnel
tunnelManager.rebuildTunnel(with: newConfig)
UserDefaults.standard.set(response.forwardsHash, forKey: "config_hash")
}
```
## 5. Hardware ID
### iOS identifierForVendor
```swift
import UIKit
let hwid = UIDevice.current.identifierForVendor?.uuidString ?? "unknown"
```
### Alternative: Custom Serial
```swift
func getHardwareId() -> String {
// Use Keychain to persist custom HWID
if let savedHwid = KeychainManager.get(key: "hwid") {
return savedHwid
}
let newHwid = UUID().uuidString
KeychainManager.set(key: "hwid", value: newHwid)
return newHwid
}
```
## 6. Versioning
### Current Version
- API Version: v1 (implicit, no version in URL)
- Agent Version: 1.0.0
### Compatibility Policy
- **Breaking Changes**: Major version bump (v2)
- **New Fields**: Added without version bump (agent ignores unknown fields)
- **Deprecation**: 6-month notice before removal
### Agent Behavior
- Agent ignores unknown fields in server response
- Agent sends only required fields in request
- Agent handles missing optional fields gracefully
## 7. Examples
### Provisioning Flow
```swift
// 1. Get hardware ID
let hwid = UIDevice.current.identifierForVendor?.uuidString ?? "unknown"
// 2. Send provisioning request
let body = ["token": token, "hwid": hwid]
let request = URLRequest(url: URL(string: "\(serverUrl)/api/v1/provision")!)
// 3. Parse encrypted config
let encrypted = try JSONDecoder().decode([String: String].self, from: data)["encrypted_config"]!
// 4. Decrypt config
let config = try CryptoManager.decryptConfig(encrypted: encrypted, hwid: hwid, salt: salt)
// 5. Store in Keychain
KeychainStorage.save(config)
// 6. Apply to WireGuard
try await tunnelManager.startTunnel(config: config)
```
### Heartbeat Flow
```swift
// 1. Get tunnel status
let tunnelUp = tunnelManager.isTunnelUp()
let lastHandshake = tunnelManager.getLastHandshake()
// 2. Send heartbeat
let body: [String: Any] = [
"device_id": config.deviceId,
"tunnel_up": tunnelUp,
"last_handshake": lastHandshake?.iso8601String ?? "1970-01-01T00:00:00Z"
]
// 3. Check for config changes
let response = try JSONDecoder().decode(HeartbeatResponse.self, from: data)
if config.configHash != response.forwardsHash {
// Config changed, reload
let newConfig = try parseConfig(from: response)
tunnelManager.rebuildTunnel(with: newConfig)
KeychainStorage.save(newConfig)
}
```
## 8. Error Handling
### HTTP Retries
```swift
func sendRequest(url: URL, body: Data, maxRetries: Int = 3) async throws -> Data {
for attempt in 1...maxRetries {
do {
let (data, response) = try await URLSession.shared.data(for: request)
if let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 {
return data
}
} catch {
if attempt == maxRetries {
throw error
}
try await Task.sleep(nanoseconds: UInt64(attempt * 1_000_000_000))
}
}
throw AgentError.heartbeatFailed("Max retries exceeded")
}
```
### Timeout Settings
- Connect timeout: 10 seconds
- Read timeout: 30 seconds
- Write timeout: 10 seconds
## 9. Security Notes
- All communication over HTTPS (TLS 1.2+)
- Registration token is single-use (consumed on first provisioning)
- Device can re-provision by obtaining new token
- Keys never logged or transmitted in plaintext
- WireGuard keys stored in iOS Keychain (hardware-backed)
+474
View File
@@ -0,0 +1,474 @@
# iOS Agent — Module Architecture
**Status**: ARCHITECTURE ONLY
**Mirror**: Android Agent (`apps/android-agent/`)
## Overview
This document defines the module architecture for the iOS WireGuard agent. The agent uses NetworkExtension (NEPacketTunnelProvider) for tunnel management and mirrors the Android agent's architecture.
## System Architecture
```
┌─────────────────────────────────────────────────────────┐
│ iOS (SwiftUI) │
├─────────────────────────────────────────────────────────┤
│ App Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ContentView│ │SettingsView│ │LogView │ │PortView │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ AgentManager │ │
│ │ (orchestrates all components) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Service Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Provisioning│ │Heartbeat│ │TunnelMgr │ │ConfigMgr│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ CryptoManager │ │
│ │ AES-256-GCM (CryptoKit) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ NetworkExtension Layer │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PacketTunnelProvider │ │
│ │ (WireGuard tunnel via wireguard-apple) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Keychain │ │ UserDefaults│ │ CoreData│ │ FileMgr │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Task Architecture
```
iOS Threads/Tasks
├── Main Thread # UI updates
├── AgentManager # Orchestration (async/await)
├── HeartbeatTask # HTTP heartbeat (30s interval)
├── TunnelManager # WireGuard tunnel management
└── BackgroundTask # BGAppRefreshTask (background refresh)
```
## State Machine
```
┌─────────────┐
│ BOOT │
└──────┬──────┘
┌─────────────┐
│ APP_INIT │ ◄──────────────────────────────┐
└──────┬──────┘ │
│ loaded │
▼ │
┌─────────────┐ fail ┌─────────────┐│
│ PROVISION │───────────────►│ APP_RETRY ││
└──────┬──────┘ └──────┬──────┘│
│ success │ │
▼ └───────┘
┌─────────────┐
│ TUNNEL_UP │
└──────┬──────┘
┌─────────────┐
│ HEARTBEAT │ ◄─── 30s interval
└──────┬──────┘
│ config_changed
┌─────────────┐
│ TUNNEL_REBUILD │
└──────┬──────┘
└──► HEARTBEAT
```
## Module Design
### 1. AgentManager
**Responsibilities**:
- Orchestrate all agent components
- Manage agent lifecycle (start/stop)
- Handle state transitions
**Key Properties**:
```swift
class AgentManager: ObservableObject {
@Published var state: AgentState = .disconnected
@Published var tunnelInfo: TunnelInfo?
@Published var logs: [LogEntry] = []
private let provisioning: ProvisioningService
private let heartbeat: HeartbeatService
private let tunnel: TunnelManager
private let config: KeychainStorage
}
```
**Key Methods**:
```swift
func startAgent() async
func stopAgent()
func rebuildTunnel(with config: WireGuardConfig) async
```
### 2. PacketTunnelProvider
**Responsibilities**:
- Handle NetworkExtension tunnel lifecycle
- Start/stop WireGuard tunnel
- Report tunnel status
**Key Methods**:
```swift
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void)
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void)
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?)
```
**WireGuard Integration**:
```swift
// Uses wireguard-apple library
let adapter = WireGuardAdapter(with: self) { logLevel, message in
Logger.log(level: logLevel, message: message)
}
adapter.start(tunnelConfiguration: tunnelConfig) { error in
if let error = error {
completionHandler(error)
} else {
completionHandler(nil)
}
}
```
### 3. TunnelManager
**Responsibilities**:
- Build WireGuard config from stored keys
- Start/stop tunnel via PacketTunnelProvider
- Monitor tunnel state
- Track last handshake time
**Key Methods**:
```swift
func startTunnel(config: WireGuardConfig) async throws
func stopTunnel()
func isTunnelUp() -> Bool
func getLastHandshake() -> Date?
```
**Config Build**:
```swift
func buildTunnelConfig(from config: WireGuardConfig) -> TunnelConfiguration {
let interface = InterfaceConfiguration(
privateKey: config.privateKey,
addresses: [config.internalIp],
dns: [config.dns]
)
let peer = PeerConfiguration(
publicKey: config.serverPub,
allowedIPs: config.allowedIps,
endpoint: config.endpoint,
preSharedKey: config.presharedKey,
persistentKeepAlive: 25
)
return TunnelConfiguration(interface: interface, peers: [peer])
}
```
### 4. HeartbeatService
**Responsibilities**:
- HTTP POST to `/api/v1/heartbeat` every 30s
- Send device status + tunnel state
- Receive config sync (detect changes)
- Trigger tunnel rebuild if config changed
**Key Methods**:
```swift
func startHeartbeatLoop()
func stopHeartbeat()
func sendHeartbeat(config: WireGuardConfig, tunnelUp: Bool, lastHandshake: Date?) async -> WireGuardConfig?
```
**HTTP Request**:
```json
POST /api/v1/heartbeat
Content-Type: application/json
{
"device_id": "uuid",
"status": "connected",
"tunnel_up": true,
"last_handshake": "2026-06-26T09:00:00Z"
}
```
**HTTP Response**:
```json
{
"device_id": "uuid",
"status": "ok",
"internal_ip": "10.172.21.2",
"private_key": "hex",
"preshared_key": "hex",
"server_pub": "hex",
"endpoint": "italy-twenty.gl.at.ply.gg:59750",
"dns": "1.1.1.1",
"allowed_ips": "0.0.0.0/0",
"server_wg_ip": "10.172.21.1",
"forwards_hash": "hash-string"
}
```
### 5. ProvisioningService
**Responsibilities**:
- HTTP POST to `/api/v1/provision`
- Parse encrypted config response
- Decrypt config via CryptoManager
- Store config in Keychain
**Key Methods**:
```swift
func provision(serverUrl: String, token: String, hwid: String) async -> WireGuardConfig?
func decryptConfig(encrypted: String, hwid: String, salt: String) -> WireGuardConfig?
```
**HTTP Request**:
```json
POST /api/v1/provision
Content-Type: application/json
{
"token": "registration-token",
"hwid": "ios-hardware-id"
}
```
**HTTP Response**:
```json
{
"encrypted_config": "base64-encoded-bytes"
}
```
### 6. CryptoManager
**Responsibilities**:
- Key derivation: SHA-256(hwid + salt)
- AES-256-GCM decryption
- Secure memory handling
**Key Methods**:
```swift
func deriveKey(token: String, hwid: String) -> SymmetricKey
func decrypt(encoded: String, key: SymmetricKey) -> Data?
```
**Algorithm**:
```swift
// Key Derivation
let input = "\(token)\(hwid)".data(using: .utf8)!
let hash = SHA256.hash(data: input)
let key = SymmetricKey(data: hash)
// Decryption
let combined = Data(base64Encoded: encoded)!
let nonce = combined.prefix(12)
let ciphertext = combined.dropFirst(12)
let sealedBox = try AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext)
let plaintext = try AES.GCM.open(sealedBox, using: key)
```
### 7. KeychainStorage
**Responsibilities**:
- Store WireGuard keys in Keychain
- Store agent config in UserDefaults
- Secure key access
**Key Methods**:
```swift
func saveConfig(_ config: AgentConfig)
func loadConfig() -> AgentConfig?
func saveWireGuardConfig(_ config: WireGuardConfig)
func loadWireGuardConfig() -> WireGuardConfig?
func clearAll()
```
**Storage Strategy**:
- **Keychain**: WireGuard keys (private_key, preshared_key)
- **UserDefaults**: Non-sensitive config (server_url, device_id, endpoint)
- **File**: Log entries (optional)
### 8. LogStore
**Responsibilities**:
- Buffer log entries in memory
- Persist to CoreData (optional)
- Provide log stream for UI
**Key Methods**:
```swift
func add(level: LogLevel, tag: String, message: String)
func clear()
func getLogs() -> [LogEntry]
```
**Log Levels**:
```swift
enum LogLevel: String {
case debug = "DEBUG"
case info = "INFO"
case warn = "WARN"
case error = "ERROR"
}
```
## Data Structures
### WireGuardConfig
```swift
struct WireGuardConfig: Codable {
let deviceId: String
let privateKey: String
let presharedKey: String
let internalIp: String
let serverPub: String
let endpoint: String
let dns: String
let allowedIps: String
let serverWgIp: String
let configHash: String
let forwardsHash: String
}
```
### AgentConfig
```swift
struct AgentConfig: Codable {
let serverUrl: String
let registrationToken: String
let isProvisioned: Bool
}
```
### AgentState
```swift
enum AgentState: String {
case disconnected = "disconnected"
case connecting = "connecting"
case connected = "connected"
case failed = "failed"
}
```
### LogEntry
```swift
struct LogEntry: Identifiable {
let id = UUID()
let timestamp: Date
let level: LogLevel
let tag: String
let message: String
}
```
## Error Handling
### Error Types
```swift
enum AgentError: Error {
case wifiNotConnected
case provisioningFailed(String)
case heartbeatFailed(String)
case cryptoDecryptionFailed
case configInvalid
case tunnelInitFailed
case keychainError(OSStatus)
}
```
### Recovery Strategy
| Error | Recovery |
|-------|----------|
| WiFi not connected | Retry 3x, then pause heartbeat |
| HTTP provision fail | Retry 3x, then notify user |
| HTTP heartbeat fail | Log only, continue |
| Crypto decrypt fail | Clear config, re-provision |
| Tunnel init fail | Retry 3x, then notify user |
| Keychain error | Use UserDefaults fallback |
## Background Tasks
### BGAppRefreshTask
iOS doesn't have boot receiver like Android. Use Background Tasks:
```swift
// Register task
BGAppRefreshTaskRequest.register(forTaskWithIdentifier: "com.nexusguard.agent.refresh", using: nil) {
task in
self.handleBackgroundRefresh(task: task as! BGAppRefreshTask)
}
// Schedule task
let request = BGAppRefreshTaskRequest(identifier: "com.nexusguard.agent.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes
try BGTaskScheduler.shared.submit(request)
```
### Background Modes
Add to `Info.plist`:
```xml
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>vpn-api</string>
</array>
```
## Dependencies
- **wireguard-apple**: WireGuard tunnel library
- **CryptoKit**: AES-256-GCM encryption
- **NetworkExtension**: VPN tunnel management
- **BackgroundTasks**: Background refresh
- **SwiftUI**: UI framework
- **Combine**: Reactive state management
## Android → iOS Mapping
| Android | iOS | Notes |
|---------|-----|-------|
| `LifecycleService` | `PacketTunnelProvider` | Background service |
| `startForeground()` | `NEPacketTunnelProvider` | Always-on tunnel |
| `SharedPreferences` | `Keychain + UserDefaults` | Config storage |
| `BroadcastReceiver` | `BGAppRefreshTask` | Background triggers |
| `lifecycleScope` | `Task {}` | Coroutine/async |
| `OkHttp` | `URLSession` | HTTP client |
| `Base64` | `Data(base64Encoded:)` | Encoding |
| `Log.d/i/w/e` | `os_log` or `print` | Logging |
+425
View File
@@ -0,0 +1,425 @@
# iOS Agent — Technical Design Document
**Version**: 1.0.0
**Date**: 2026-07-06
**Status**: Draft
**Mirror**: Android Agent (`apps/android-agent/`)
## 1. Executive Summary
NexusGuard iOS Agent is an iOS-based WireGuard client for mobile devices. It mirrors the Android agent's architecture, UI design, and heartbeat flow, using iOS-native APIs (NetworkExtension, CryptoKit, Keychain).
## 2. Goals & Non-Goals
### Goals
- iOS-based WireGuard tunnel (NetworkExtension)
- Auto-provisioning via HTTP (same API as Android/ESP32)
- Heartbeat monitoring with config sync (30s interval)
- Keychain-based config storage (hardware-backed encryption)
- SwiftUI UI matching Android agent design
- Background operation via NetworkExtension
### Non-Goals
- gRPC support (not needed for mobile)
- Port forwarding (userspace agent only)
- Jailbroken device support
- macOS/iPadOS support (iPhone only for v1)
## 3. Architecture
### 3.1 System Architecture
```
┌─────────────────────────────────────────────────────────┐
│ iOS (SwiftUI) │
├─────────────────────────────────────────────────────────┤
│ App Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ContentView│ │SettingsView│ │LogView │ │PortView │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ AgentManager │ │
│ │ (orchestrates all components) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Service Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Provisioning│ │Heartbeat│ │TunnelMgr │ │ConfigMgr│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ CryptoManager │ │
│ │ AES-256-GCM (CryptoKit) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ NetworkExtension Layer │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PacketTunnelProvider │ │
│ │ (WireGuard tunnel via wireguard-apple) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Keychain │ │ UserDefaults│ │ CoreData│ │ FileMgr │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 3.2 Task Architecture
```
iOS Threads/Tasks
├── Main Thread # UI updates
├── AgentManager # Orchestration (async/await)
├── HeartbeatTask # HTTP heartbeat (30s interval)
├── TunnelManager # WireGuard tunnel management
└── BackgroundTask # BGAppRefreshTask (background refresh)
```
### 3.3 State Machine
```
┌─────────────┐
│ BOOT │
└──────┬──────┘
┌─────────────┐
│ APP_INIT │ ◄──────────────────────────────┐
└──────┬──────┘ │
│ loaded │
▼ │
┌─────────────┐ fail ┌─────────────┐│
│ PROVISION │───────────────►│ APP_RETRY ││
└──────┬──────┘ └──────┬──────┘│
│ success │ │
▼ └───────┘
┌─────────────┐
│ TUNNEL_UP │
└──────┬──────┘
┌─────────────┐
│ HEARTBEAT │ ◄─── 30s interval
└──────┬──────┘
│ config_changed
┌─────────────┐
│ TUNNEL_REBUILD │
└──────┬──────┘
└──► HEARTBEAT
```
## 4. Module Design
### 4.1 AgentManager
**Responsibilities**:
- Orchestrate all agent components
- Manage agent lifecycle (start/stop)
- Handle state transitions
**Key Properties**:
```swift
class AgentManager: ObservableObject {
@Published var state: AgentState = .disconnected
@Published var tunnelInfo: TunnelInfo?
@Published var logs: [LogEntry] = []
private let provisioning: ProvisioningService
private let heartbeat: HeartbeatService
private let tunnel: TunnelManager
private let config: KeychainStorage
}
```
**Key Methods**:
```swift
func startAgent() async
func stopAgent()
func rebuildTunnel(with config: WireGuardConfig) async
```
### 4.2 PacketTunnelProvider
**Responsibilities**:
- Handle NetworkExtension tunnel lifecycle
- Start/stop WireGuard tunnel
- Report tunnel status
**Key Methods**:
```swift
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void)
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void)
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?)
```
### 4.3 TunnelManager
**Responsibilities**:
- Build WireGuard config from stored keys
- Start/stop tunnel via PacketTunnelProvider
- Monitor tunnel state
- Track last handshake time
**Key Methods**:
```swift
func startTunnel(config: WireGuardConfig) async throws
func stopTunnel()
func isTunnelUp() -> Bool
func getLastHandshake() -> Date?
```
### 4.4 HeartbeatService
**Responsibilities**:
- HTTP POST to `/api/v1/heartbeat` every 30s
- Send device status + tunnel state
- Receive config sync (detect changes)
- Trigger tunnel rebuild if config changed
**Key Methods**:
```swift
func startHeartbeatLoop()
func stopHeartbeat()
func sendHeartbeat(config: WireGuardConfig, tunnelUp: Bool, lastHandshake: Date?) async -> WireGuardConfig?
```
### 4.5 ProvisioningService
**Responsibilities**:
- HTTP POST to `/api/v1/provision`
- Parse encrypted config response
- Decrypt config via CryptoManager
- Store config in Keychain
**Key Methods**:
```swift
func provision(serverUrl: String, token: String, hwid: String) async -> WireGuardConfig?
func decryptConfig(encrypted: String, hwid: String, salt: String) -> WireGuardConfig?
```
### 4.6 CryptoManager
**Responsibilities**:
- Key derivation: SHA-256(hwid + salt)
- AES-256-GCM decryption
- Secure memory handling
**Key Methods**:
```swift
func deriveKey(token: String, hwid: String) -> SymmetricKey
func decrypt(encoded: String, key: SymmetricKey) -> Data?
```
### 4.7 KeychainStorage
**Responsibilities**:
- Store WireGuard keys in Keychain
- Store agent config in UserDefaults
- Secure key access
**Key Methods**:
```swift
func saveConfig(_ config: AgentConfig)
func loadConfig() -> AgentConfig?
func saveWireGuardConfig(_ config: WireGuardConfig)
func loadWireGuardConfig() -> WireGuardConfig?
func clearAll()
```
### 4.8 LogStore
**Responsibilities**:
- Buffer log entries in memory
- Persist to CoreData (optional)
- Provide log stream for UI
**Key Methods**:
```swift
func add(level: LogLevel, tag: String, message: String)
func clear()
func getLogs() -> [LogEntry]
```
## 5. Data Structures
### 5.1 WireGuardConfig
```swift
struct WireGuardConfig: Codable {
let deviceId: String
let privateKey: String
let presharedKey: String
let internalIp: String
let serverPub: String
let endpoint: String
let dns: String
let allowedIps: String
let serverWgIp: String
let configHash: String
let forwardsHash: String
}
```
### 5.2 AgentConfig
```swift
struct AgentConfig: Codable {
let serverUrl: String
let registrationToken: String
let isProvisioned: Bool
}
```
### 5.3 AgentState
```swift
enum AgentState: String {
case disconnected = "disconnected"
case connecting = "connecting"
case connected = "connected"
case failed = "failed"
}
```
### 5.4 LogEntry
```swift
struct LogEntry: Identifiable {
let id = UUID()
let timestamp: Date
let level: LogLevel
let tag: String
let message: String
}
```
## 6. Error Handling
### 6.1 Error Types
```swift
enum AgentError: Error {
case wifiNotConnected
case provisioningFailed(String)
case heartbeatFailed(String)
case cryptoDecryptionFailed
case configInvalid
case tunnelInitFailed
case keychainError(OSStatus)
}
```
### 6.2 Recovery Strategy
| Error | Recovery |
|-------|----------|
| WiFi not connected | Retry 3x, then pause heartbeat |
| HTTP provision fail | Retry 3x, then notify user |
| HTTP heartbeat fail | Log only, continue |
| Crypto decrypt fail | Clear config, re-provision |
| Tunnel init fail | Retry 3x, then notify user |
| Keychain error | Use UserDefaults fallback |
## 7. Background Tasks
### 7.1 BGAppRefreshTask
iOS doesn't have boot receiver like Android. Use Background Tasks:
```swift
// Register task
BGAppRefreshTaskRequest.register(forTaskWithIdentifier: "com.nexusguard.agent.refresh", using: nil) {
task in
self.handleBackgroundRefresh(task: task as! BGAppRefreshTask)
}
// Schedule task
let request = BGAppRefreshTaskRequest(identifier: "com.nexusguard.agent.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes
try BGTaskScheduler.shared.submit(request)
```
### 7.2 Background Modes
Add to `Info.plist`:
```xml
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>vpn-api</string>
</array>
```
## 8. Security Considerations
### 8.1 Key Storage
- WireGuard private keys stored in iOS Keychain (hardware-backed)
- Non-sensitive config in UserDefaults
- Never log keys or tokens
### 8.2 TLS
- Server communication over HTTPS (TLS 1.2+)
- Use `URLSession` with default TLS settings
- Certificate pinning (optional, future)
### 8.3 Hardware ID
- Use `identifierForVendor` for device identification
- Persists across app reinstalls until device factory reset
- Used for key derivation (HWID + salt)
## 9. Testing Strategy
### 9.1 Unit Tests
- Crypto: Decrypt known ciphertext with known key
- Config: Keychain read/write cycle
- Heartbeat: Parse server response
### 9.2 Integration Tests
- Provision → Tunnel → Heartbeat cycle
- Config change detection
- Tunnel rebuild on config change
### 9.3 UI Tests
- VPN toggle interaction
- Tab switching
- Settings save/load
### 9.4 Device Tests
- iPhone 12+ running iOS 16+
- NetworkExtension background operation
- Keychain access in background
## 10. Future Enhancements
### 10.1 Phase 2
- iPadOS support
- Widget for quick status
- Shortcuts integration
- Siri commands
### 10.2 Phase 3
- macOS Catalyst support
- Apple Watch companion
- CarPlay integration
## 11. References
- [Apple NetworkExtension Documentation](https://developer.apple.com/documentation/networkextension)
- [WireGuard for iOS](https://github.com/WireGuard/wireguard-apple)
- [CryptoKit Documentation](https://developer.apple.com/cryptokit/)
- [Background Tasks](https://developer.apple.com/documentation/backgroundtasks)
- [NexusGuard Server API](../../server-core/docs/)
+506
View File
@@ -0,0 +1,506 @@
# iOS Agent — UI Design Specs
**Status**: DESIGN ONLY
**Mirror**: Android Agent (`apps/android-agent/`)
## Overview
This document defines the UI design for the iOS WireGuard agent. It mirrors the Android agent's design, colors, layout, and user experience.
## Color Palette
### Status Colors
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| `status_disconnected` | `#FFB0B0B0` | (176, 176, 176) | Disconnected state |
| `status_connecting` | `#FFFFD93D` | (255, 217, 61) | Connecting state |
| `status_connected` | `#FF4CAF50` | (76, 175, 80) | Connected state |
| `status_failed` | `#FFFF6B6B` | (255, 107, 107) | Failed state |
### UI Colors
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| `card_bg` | `#FF1A1A2E` | (26, 26, 46) | Card background |
| `card_bg_dark` | `#FF0F0F1A` | (15, 15, 26) | Badge background |
| `text_primary` | `#FFFFFFFF` | (255, 255, 255) | Primary text |
| `text_secondary` | `#FFB0B0B0` | (176, 176, 176) | Secondary text |
| `accent_green` | `#FF4CAF50` | (76, 175, 80) | Accent/highlight |
| `accent_blue` | `#FF2196F3` | (33, 150, 243) | Links/actions |
| `border_color` | `#FF2A2A3E` | (42, 42, 62) | Card borders |
### Log Colors
| Level | Hex | RGB |
|-------|-----|-----|
| `DEBUG` | `#FF6B6B6B` | (107, 107, 107) |
| `INFO` | `#FFB0B0B0` | (176, 176, 176) |
| `WARN` | `#FFFFD93D` | (255, 217, 61) |
| `ERROR` | `#FFFF6B6B` | (255, 107, 107) |
## Color Definitions (Swift)
```swift
import SwiftUI
extension Color {
// Status colors
static let statusDisconnected = Color(red: 0.69, green: 0.69, blue: 0.69) // #B0B0B0
static let statusConnecting = Color(red: 1.0, green: 0.85, blue: 0.24) // #FFD93D
static let statusConnected = Color(red: 0.30, green: 0.69, blue: 0.31) // #4CAF50
static let statusFailed = Color(red: 1.0, green: 0.42, blue: 0.42) // #FF6B6B
// UI colors
static let cardBackground = Color(red: 0.10, green: 0.10, blue: 0.18) // #1A1A2E
static let cardBackgroundDark = Color(red: 0.06, green: 0.06, blue: 0.10) // #0F0F1A
static let textPrimary = Color.white // #FFFFFF
static let textSecondary = Color(red: 0.69, green: 0.69, blue: 0.69) // #B0B0B0
static let accentGreen = Color(red: 0.30, green: 0.69, blue: 0.31) // #4CAF50
static let accentBlue = Color(red: 0.13, green: 0.59, blue: 0.95) // #2196F3
static let borderColor = Color(red: 0.16, green: 0.16, blue: 0.24) // #2A2A3E
}
```
## Layout Structure
### Main Screen (ContentView)
```
┌─────────────────────────────────────┐
│ NexusGuard [⚙️ Settings]│
├─────────────────────────────────────┤
│ │
│ ┌─────────────────────────────┐ │
│ │ ● STATUS: Connected │ │
│ │ │ │
│ │ IP Address 10.172.21.2 │ │
│ │ Admin Web https://... │ │
│ │ Device ID abc-123-... │ │
│ │ Handshake 14:32:05 │ │
│ │ Allowed IPs 0.0.0.0/0 │ │
│ │ Transport HTTPS │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ [Status] [Ports] [Log] │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Port Forwards: 2 │ │
│ │ TCP :8080 → 10.172.21.2:80│ │
│ │ UDP :53 → 10.172.21.2:53│ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ [Copy Log] [Clear Log] │ │
│ │ │ │
│ │ 14:32:05 [INFO] Agent... │ │
│ │ 14:32:05 [INFO] Tunnel... │ │
│ │ 14:32:05 [WARN] Retry... │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ ══════════════════ │ │
│ │ VPN Toggle │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
```
### Settings Screen (SettingsView)
```
┌─────────────────────────────────────┐
│ ← Settings │
├─────────────────────────────────────┤
│ │
│ Server URL │
│ ┌─────────────────────────────┐ │
│ │ https://api-nexus.datadunia.com│ │
│ └─────────────────────────────┘ │
│ │
│ Registration Token │
│ ┌─────────────────────────────┐ │
│ │ •••••••••••••••• │ │
│ └─────────────────────────────┘ │
│ │
│ Auto-Start on Boot │
│ ┌─────────────────────────────┐ │
│ │ [Toggle] │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Save Settings │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Export Config │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Import Config │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Paste Config │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
```
## Component Specs
### Status Card
```swift
struct StatusCard: View {
let state: AgentState
let config: WireGuardConfig?
var body: some View {
VStack(alignment: .leading, spacing: 12) {
// Status indicator
HStack {
Circle()
.fill(statusColor)
.frame(width: 12, height: 12)
Text(statusText)
.font(.headline)
.foregroundColor(statusColor)
}
// Info fields
if state == .connected {
InfoRow(label: "IP Address", value: config?.internalIp ?? "-")
InfoRow(label: "Admin Web", value: "https://api-nexus.datadunia.com")
InfoRow(label: "Device ID", value: config?.deviceId ?? "-")
InfoRow(label: "Handshake", value: lastHandshake ?? "-")
InfoRow(label: "Allowed IPs", value: config?.allowedIps ?? "-")
InfoRow(label: "Transport", value: transport)
}
}
.padding()
.background(Color.cardBackground)
.cornerRadius(12)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(Color.borderColor, lineWidth: 1)
)
}
private var statusColor: Color {
switch state {
case .disconnected: return .statusDisconnected
case .connecting: return .statusConnecting
case .connected: return .statusConnected
case .failed: return .statusFailed
}
}
}
```
### Info Row
```swift
struct InfoRow: View {
let label: String
let value: String
var body: some View {
HStack {
Text(label)
.font(.subheadline)
.foregroundColor(.textSecondary)
Spacer()
Text(value)
.font(.subheadline)
.foregroundColor(.textPrimary)
.lineLimit(1)
}
}
}
```
### Port Forward Row
```swift
struct PortForwardRow: View {
let forward: PortForward
var body: some View {
HStack {
Text(forward.protocol.uppercased())
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.cardBackgroundDark)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.accentGreen)
.cornerRadius(4)
Text(":\(forward.publicPort)")
.font(.subheadline)
.foregroundColor(.textPrimary)
.font(.system(.body, design: .monospaced))
Spacer()
Text("\(forward.targetIp):\(forward.targetPort)")
.font(.subheadline)
.foregroundColor(.textSecondary)
.font(.system(.body, design: .monospaced))
}
}
}
```
### Log Entry
```swift
struct LogEntryView: View {
let entry: LogEntry
var body: some View {
HStack(alignment: .top) {
Text(entry.timestamp, style: .time)
.font(.system(.caption, design: .monospaced))
.foregroundColor(entry.level.color)
Text("[\(entry.level.rawValue)]")
.font(.system(.caption, design: .monospaced))
.foregroundColor(entry.level.color)
Text("\(entry.tag): \(entry.message)")
.font(.system(.caption, design: .monospaced))
.foregroundColor(entry.level.color)
}
}
}
extension LogLevel {
var color: Color {
switch self {
case .debug: return Color(red: 0.42, green: 0.42, blue: 0.42) // #6B6B6B
case .info: return Color(red: 0.69, green: 0.69, blue: 0.69) // #B0B0B0
case .warn: return Color(red: 1.0, green: 0.85, blue: 0.24) // #FFD93D
case .error: return Color(red: 1.0, green: 0.42, blue: 0.42) // #FF6B6B
}
}
}
```
### VPN Toggle
```swift
struct VPNToggle: View {
@Binding var isOn: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
RoundedRectangle(cornerRadius: 25)
.fill(isOn ? Color.accentGreen : Color.statusDisconnected)
.frame(height: 50)
.overlay(
HStack {
Circle()
.fill(Color.white)
.frame(width: 40, height: 40)
.offset(x: isOn ? 40 : -40)
Spacer()
}
)
}
.buttonStyle(PlainButtonStyle())
}
}
```
## Tab Layout
```swift
struct TabBar: View {
@Binding var selectedTab: Int
var body: some View {
HStack(spacing: 0) {
TabButton(title: "Status", isSelected: selectedTab == 0) {
selectedTab = 0
}
TabButton(title: "Ports", isSelected: selectedTab == 1) {
selectedTab = 1
}
TabButton(title: "Log", isSelected: selectedTab == 2) {
selectedTab = 2
}
}
.background(Color.cardBackground)
.cornerRadius(8)
}
}
struct TabButton: View {
let title: String
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.subheadline)
.foregroundColor(isSelected ? .textPrimary : .textSecondary)
.padding(.vertical, 12)
.frame(maxWidth: .infinity)
.background(isSelected ? Color.accentGreen : Color.clear)
.cornerRadius(8)
}
}
}
```
## Typography
### Font Sizes
| Element | Size | Weight | Design |
|---------|------|--------|--------|
| Status text | 18pt | Semibold | Default |
| Info label | 14pt | Regular | Default |
| Info value | 14pt | Regular | Monospaced |
| Tab text | 14pt | Medium | Default |
| Log text | 12pt | Regular | Monospaced |
| Badge text | 10pt | Bold | Default |
### Font Definitions
```swift
extension Font {
static let statusText = Font.headline
static let infoLabel = Font.subheadline
static let infoValue = Font.subheadline.monospaced()
static let tabText = Font.subheadline.weight(.medium)
static let logText = Font.caption.monospaced()
static let badgeText = Font.caption2.bold()
}
```
## Spacing
| Element | Value |
|---------|-------|
| Card padding | 16pt |
| Card corner radius | 12pt |
| Card border width | 1pt |
| Row spacing | 12pt |
| Badge padding | 8pt horizontal, 4pt vertical |
| Badge corner radius | 4pt |
| Button height | 50pt |
| Button corner radius | 25pt |
## Animation
### Status Change
```swift
withAnimation(.easeInOut(duration: 0.3)) {
state = .connected
}
```
### Tab Switch
```swift
withAnimation(.easeInOut(duration: 0.2)) {
selectedTab = 1
}
```
### VPN Toggle
```swift
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
isVPNOn.toggle()
}
```
## Haptic Feedback
```swift
// Success
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
// Error
UINotificationFeedbackGenerator().notificationOccurred(.error)
// Selection
UISelectionFeedbackGenerator().selectionChanged()
```
## Dark Mode
iOS agent uses dark mode only (matches Android design). Force dark mode:
```swift
@main
struct NexusGuardApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(.dark)
}
}
}
```
## Responsive Design
### iPhone SE
- Compact layout
- Smaller font sizes
- Reduced padding
### iPhone 14/15
- Standard layout
- Default font sizes
- Standard padding
### iPhone 14/15 Pro Max
- Expanded layout
- Larger font sizes
- Increased padding
## Accessibility
### VoiceOver
```swift
Text("Connected")
.accessibilityLabel("VPN Status: Connected")
.accessibilityHint("Double tap to toggle VPN")
InfoRow(label: "IP Address", value: "10.172.21.2")
.accessibilityElement(children: .combine)
.accessibilityLabel("IP Address: 10.172.21.2")
```
### Dynamic Type
```swift
Text("Status")
.font(.headline)
.dynamicTypeSize(...DynamicTypeSize.accessibility2)
```
### Reduce Motion
```swift
@Environment(\.accessibilityReduceMotion) var reduceMotion
withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.3)) {
state = .connected
}
```
-51
View File
@@ -1,51 +0,0 @@
#!/bin/bash
set -e
echo "========================================="
echo " NexusGuard SD-WAN: INITIAL SETUP "
echo "========================================="
# 1. Environment File Generation
if [ ! -f .env ]; then
echo "[!] .env file not found."
if [ -f .env.example ]; then
echo "[+] Auto-generating .env from .env.example..."
cp .env.example .env
# Generate secure random hex keys
RANDOM_JWT=$(openssl rand -hex 32)
RANDOM_SALT=$(openssl rand -hex 32)
sed -i "s/GantiDenganKunciRahasiaJWTUntukDashboard256Bit/$RANDOM_JWT/g" .env
sed -i "s/GantiDenganKunciGaramUntukWireGuard256Bit/$RANDOM_SALT/g" .env
echo "[+] Secure cryptographic keys have been auto-generated!"
else
echo "[-] ERROR: .env.example is missing. Cannot generate .env."
exit 1
fi
else
echo "[+] .env file already exists. Skipping environment generation."
fi
# Make sure Docker Compose v2 is available
if ! docker compose version &> /dev/null; then
echo "[-] ERROR: docker compose (v2) is not available. Please install Docker and Docker Compose plugin."
exit 1
fi
echo ""
echo "========================================="
echo " Setup complete! Next Steps: "
echo " 1. Open and review the '.env' file. "
echo " Make sure to change DB_PASSWORD, "
echo " API_PORT, and VITE_API_BASE_URL. "
echo " "
echo " 2. Run 'bash update.sh' to download the "
echo " latest code, build, and start the "
echo " system. The script will also run "
echo " database migrations automatically. "
echo " "
echo " 3. Create your Admin account: "
echo " docker exec -it nexus-guard-suite-server-core-1 ./server-core -create-admin -user \"admin\" -pass \"PasswordKuat123!\""
echo "========================================="
+27 -8
View File
@@ -32,11 +32,29 @@ while [[ $# -gt 0 ]]; do
esac esac
done done
# 1. Pastikan file env ada # 1. Auto-generate .env jika belum ada (seamless first-run)
if [ ! -f .env ]; then if [ ! -f .env ]; then
echo "[-] ERROR: .env file not found. Please run ./setup.sh first to initialize configuration." echo "[!] .env file not found."
if [ -f .env.example ]; then
echo "[+] Auto-generating .env from .env.example..."
cp .env.example .env
# Generate secure random hex keys
RANDOM_JWT=$(openssl rand -hex 32)
RANDOM_SALT=$(openssl rand -hex 32)
sed -i "s/GantiDenganKunciRahasiaJWTUntukDashboard256Bit/$RANDOM_JWT/g" .env
sed -i "s/GantiDenganKunciGaramUntukWireGuard256Bit/$RANDOM_SALT/g" .env
echo "[+] Secure cryptographic keys have been auto-generated!"
echo "[+] Review .env file before continuing. Edit DB_PASSWORD, API_PORT, VITE_API_BASE_URL as needed."
else
echo "[-] ERROR: .env.example is missing. Cannot generate .env."
exit 1 exit 1
fi fi
else
echo "[+] .env file already exists. Skipping environment generation."
fi
# 2. Update Code dari Git (Main & Submodules) # 2. Update Code dari Git (Main & Submodules)
echo "[+] Pulling latest source code from repository..." echo "[+] Pulling latest source code from repository..."
@@ -56,8 +74,9 @@ else
echo "[!] apps/docs directory not found. Skipping docs build." echo "[!] apps/docs directory not found. Skipping docs build."
fi fi
# 3. Hitung state hash (git + .env) untuk deteksi perubahan # 3. Hitung state hash (hanya server-core + dashboard-ui untuk rebuild detection)
CURRENT_HASH=$(echo "$(git rev-parse HEAD 2>/dev/null)$(git submodule status 2>/dev/null)$(sha256sum .env 2>/dev/null)" | sha256sum | cut -d" " -f1) # device-agent dan device-agent-embedded build terpisah, tidak mempengaruhi Docker build
DOCKER_HASH=$(echo "$(git rev-parse HEAD 2>/dev/null)$(git -C apps/server-core rev-parse HEAD 2>/dev/null)$(git -C apps/dashboard-ui rev-parse HEAD 2>/dev/null)$(sha256sum .env 2>/dev/null)" | sha256sum | cut -d" " -f1)
# 4. Tentukan apakah perlu rebuild # 4. Tentukan apakah perlu rebuild
NEEDS_REBUILD=false NEEDS_REBUILD=false
@@ -72,11 +91,11 @@ elif [ ! -f "$STATE_FILE" ]; then
NEEDS_REBUILD=true NEEDS_REBUILD=true
else else
PREVIOUS_HASH=$(cat "$STATE_FILE" 2>/dev/null || echo "") PREVIOUS_HASH=$(cat "$STATE_FILE" 2>/dev/null || echo "")
if [ "$CURRENT_HASH" != "$PREVIOUS_HASH" ]; then if [ "$DOCKER_HASH" != "$PREVIOUS_HASH" ]; then
echo "[+] State hash changed. Rebuilding." echo "[+] Docker components changed. Rebuilding."
NEEDS_REBUILD=true NEEDS_REBUILD=true
else else
echo "[-] No changes detected. Skipping build and restart." echo "[-] No Docker changes detected. Skipping build and restart."
fi fi
fi fi
@@ -160,7 +179,7 @@ if [ "$NEEDS_REBUILD" = true ]; then
docker image prune -f docker image prune -f
# Simpan state secara atomik (tmp + mv untuk cegah korupsi file) # Simpan state secara atomik (tmp + mv untuk cegah korupsi file)
echo "$CURRENT_HASH" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE" echo "$DOCKER_HASH" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
echo "=========================================" echo "========================================="
echo " Update & Deployment complete! " echo " Update & Deployment complete! "