chore: CI split dev/prod workflows, archive old plans, new plan docs
NexusGuard CI/CD / server-core-test (push) Failing after 33s
NexusGuard CI/CD / server-core-build (push) Has been skipped
NexusGuard CI/CD / device-agent-test (push) Failing after 35s
NexusGuard CI/CD / device-agent-cross-build (amd64, linux) (push) Has been skipped
NexusGuard CI/CD / device-agent-cross-build (amd64, windows) (push) Has been skipped
NexusGuard CI/CD / device-agent-cross-build (arm64, linux) (push) Has been skipped
NexusGuard CI/CD / dashboard-test (push) Failing after 30s
NexusGuard CI/CD / dashboard-dist (push) Has been skipped
NexusGuard CI/CD / release (push) Has been skipped

This commit is contained in:
datadunia
2026-05-29 08:01:44 +07:00
parent 15b09c8408
commit 6481a13caf
14 changed files with 1919 additions and 48 deletions
+6 -33
View File
@@ -2,10 +2,9 @@ name: NexusGuard CI/CD
on:
push:
branches: [main]
tags: ['v*', 'dev-*']
pull_request:
branches: [main]
tags:
- 'v*'
- 'dev-*'
jobs:
# ──────────────────────────────────────────────
@@ -149,36 +148,10 @@ jobs:
release:
runs-on: ubuntu-latest
needs: [server-core-build, device-agent-cross-build, dashboard-dist]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Prepare release assets
run: |
mkdir -p release/
# Device agent binaries
cp artifacts/nexus-device-agent-*/nexus-device-agent-* release/ 2>/dev/null || true
# Server core binary
cp artifacts/server-core-linux-amd64/server-core release/nexus-server-core-linux-amd64 || true
chmod +x release/nexus-server-core-linux-amd64
# Dashboard dist (zip)
cd artifacts/dashboard-ui-dist && zip -r ../../release/nexusguard-dashboard-ui.zip . && cd ../../..
# Checksums
cd release && sha256sum * > checksums-sha256.txt
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
release/*
- name: Echo release step
run: echo "Release step for Gitea"
@@ -0,0 +1,487 @@
# Plan: Firewall INPUT Fix + Device Status via WireGuard Handshake
## TL;DR
> Fix two critical production issues: (1) firewall INPUT chain doesn't isolate WireGuard peers, (2) device status shows offline because it depends on Redis heartbeats instead of WireGuard handshake data.
**Deliverables**:
- WireGuard per-peer handshake status (replace Redis heartbeat dependency)
- Firewall INPUT chain rules in code (persist across restarts)
- ICMP + established/related accept rules for WG traffic
**Estimated Effort**: Medium
**Parallel Execution**: YES - 2 waves
**Critical Path**: T1 (wgctrl) → T2 (SyncToDB) → T3 (INPUT rules) → F1-F4
---
## Context
### Original Request
1. Device `gogo2` connected via WireGuard (peer shows handshake in `wg show`) but dashboard shows offline (`IsActive=false`)
2. Firewall INPUT chain rules don't work — ping from WG peer still works even with drop rules
3. User wants a proper plan, not ad-hoc fixes
### Interview Summary
**Key Discussions**:
- Device was created manually via API, not via device-agent → no heartbeats sent
- Heartbeat system: device-agent → POST /api/v1/heartbeat → Redis TTL 90s → SyncToDB sets `is_active`
- Without device-agent, no heartbeats → `is_active` stays false
- Solution: use WireGuard handshake time from wgctrl instead of Redis heartbeats
- Firewall: `table ip nexusguard` INPUT chain has rules but ping still works
- Root cause: rules not in code (manually added, lost on restart), ICMP not explicitly allowed, possible `table inet` vs `table ip` priority conflict
**Research Findings**:
- `wgmanager_linux.go:50` iterates `dev.Peers` and reads `peer.LastHandshakeTime` — but only stores MAX across all peers, losing per-peer granularity
- `WgStatus` struct has no per-peer handshake map
- `SyncToDB()` in `heartbeat/redis.go` checks Redis key existence → sets `is_active`
- Dashboard reads `device.IsActive` from DB — no frontend changes needed if SyncToDB is fixed
- `InitNetwork()` creates INPUT chain with zero rules — all rules were manual
- `AddInputRule()` only adds `udp dport` rules — no ICMP, no established/related
### Metis Review
**Identified Gaps** (addressed):
- INPUT chain rules must be added to `InitNetwork()` code for persistence
- ICMP accept rule missing — only TCP/UDP explicitly allowed
- Per-peer handshake data needed (current `GetStatus()` loses per-peer granularity)
- Remote node devices won't have local WG data — scoped to local-only
- Docker's `inet filter` chains must not be modified
---
## Work Objectives
### Core Objective
Device status reflects actual WireGuard connectivity (not Redis heartbeats), and firewall INPUT chain properly isolates WG peers.
### Concrete Deliverables
- `wgmanager` interface: new `GetPeerHandshakes()` method
- `heartbeat/redis.go`: `SyncToDB()` uses WG handshake data
- `nftables_linux.go`: INPUT chain rules in `InitNetwork()`
- Production deployment with verification
### Definition of Done
- [x] Device with active WG peer shows `is_active=true` in API
- [x] Device without WG handshake shows `is_active=false`
- [x] `ping 10.172.21.1` from WG peer → RTO (dropped)
- [x] TCP 8080 from WG peer → works (API access)
- [x] UDP 51820 from WG peer → works (WG tunnel)
- [x] Non-WG traffic → unaffected (Docker, SSH, etc.)
- [x] Rules persist across server restart
### Must Have
- `GetPeerHandshakes() map[string]time.Time` in wgmanager
- `SyncToDB()` matches device public keys to WG peer public keys
- INPUT chain rules in `InitNetwork()` code (not manual)
- ICMP accept rule for WG traffic
- established/related accept rule for WG traffic
### Must NOT Have (Guardrails)
- NEVER `nft flush table` — project anti-pattern
- NEVER change `policy accept` on any chain
- NEVER modify Docker's `inet filter` chains
- NEVER add IPv6 rules
- NEVER remove heartbeat API endpoint (keep as fallback)
- NEVER add new API endpoints — modify existing only
- NEVER touch `shared/crypto/encryptor.go`
- NEVER add per-device firewall INPUT rules (INPUT is server-level, not peer isolation)
---
## Verification Strategy
> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed.
### Test Decision
- **Infrastructure exists**: YES (Go build, nft CLI, curl)
- **Automated tests**: Tests-after (verify existing tests still pass)
- **Framework**: `go build -tags dev ./...`, `nft list chain`, `curl` API
### QA Policy
Every task includes agent-executed QA scenarios.
Evidence saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.{ext}`.
- **Backend**: Use Bash (curl) — API requests, assert status + response fields
- **Firewall**: Use Bash (ssh + nft) — rule verification, counter checks
- **WireGuard**: Use Bash (ssh + wg) — handshake verification
---
## Execution Strategy
### Parallel Execution Waves
```
Wave 1 (Start Immediately - backend core):
├── Task 1: wgmanager GetPeerHandshakes() [quick]
├── Task 2: SyncToDB uses WG handshake [quick]
└── Task 3: InitNetwork INPUT chain rules [quick]
Wave 2 (After Wave 1 - deployment + verification):
├── Task 4: Build + deploy to production [quick]
└── Task 5: Verify all acceptance criteria [quick]
Wave FINAL (After ALL tasks — 4 parallel reviews, then user okay):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA (unspecified-high)
└── Task F4: Scope fidelity check (deep)
-> Present results -> Get explicit user okay
```
### Dependency Matrix
| Task | Depends On | Blocks |
|------|-----------|--------|
| T1 | None | T2 |
| T2 | T1 | T4 |
| T3 | None | T4 |
| T4 | T2, T3 | T5, F1-F4 |
| T5 | T4 | F1-F4 |
### Agent Dispatch Summary
- **Wave 1**: 3 tasks — T1 `quick`, T2 `quick`, T3 `quick`
- **Wave 2**: 2 tasks — T4 `quick`, T5 `quick`
- **FINAL**: 4 tasks — F1 `oracle`, F2 `unspecified-high`, F3 `unspecified-high`, F4 `deep`
---
## TODOs
- [x] 1. **wgmanager: add GetPeerHandshakes() method**
**What to do**:
- Add `GetPeerHandshakes() map[string]time.Time` to `WgManager` interface in `internal/wgmanager/manager.go`
- Implement in `internal/wgmanager/wgmanager_linux.go`: iterate `dev.Peers`, return map of `peer.PublicKey.String() → peer.LastHandshakeTime`
- Implement stub in `internal/wgmanager/wgmanager_stub.go` (returns empty map)
- Add test in `internal/wgmanager/` if test file exists
**Must NOT do**:
- Don't modify existing `GetStatus()` method
- Don't change `WgStatus` struct
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES (with T3)
- **Parallel Group**: Wave 1 (with T3)
- **Blocks**: T2
- **Blocked By**: None
**References**:
- `apps/server-core/internal/wgmanager/manager.go:27-39` — WgManager interface + UpConfig struct
- `apps/server-core/internal/wgmanager/wgmanager_linux.go:27-58` — GetStatus() implementation, iterates dev.Peers at line 50
- `apps/server-core/internal/wgmanager/wgmanager_stub.go` — Stub implementation
**Acceptance Criteria**:
- [x] `go build -tags dev ./...` passes
- [x] `GetPeerHandshakes()` returns non-empty map when peers are connected
- [x] Method is on both interface and implementations
**QA Scenarios**:
```
Scenario: GetPeerHandshakes returns peer handshake data
Tool: Bash (go test or manual verification)
Preconditions: Server running with connected WG peer
Steps:
1. SSH to server
2. Run: docker exec nexus-guard-suite-server-core-1 ./server-core -tags dev -test.run TestGetPeerHandshakes 2>&1 || echo "verify via API"
3. Check wg show wg0 latest-handshakes for reference
Expected Result: Map contains public key → timestamp for connected peer
Evidence: .sisyphus/evidence/task1-peer-handshakes.json
```
**Commit**: YES
- Message: `feat(wgmanager): add GetPeerHandshakes() for per-peer handshake data`
- Files: `internal/wgmanager/manager.go`, `internal/wgmanager/wgmanager_linux.go`, `internal/wgmanager/wgmanager_stub.go`
- [x] 2. **heartbeat: SyncToDB uses WireGuard handshake instead of Redis**
**What to do**:
- Modify `SyncToDB()` in `internal/heartbeat/redis.go` to:
1. Query `wgMgr.GetPeerHandshakes()` (inject wgMgr into Manager or pass as parameter)
2. For each device, match `device.PublicKey` against WG peer public keys
3. If match found AND handshake within 120s → set `is_active=true`, `last_handshake=handshake_time`
4. If no match OR handshake > 120s → set `is_active=false`
- Keep Redis heartbeat recording active (don't break existing device-agents)
- Update `NewHeartbeatManager` to accept `wgmanager.WgManager` parameter
- Update `main.go` to pass `wgMgr` to heartbeat manager
**Must NOT do**:
- Don't remove Redis heartbeat recording (keep as fallback)
- Don't change the heartbeat API endpoint
- Don't change `RecordPing()` method
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 1 (after T1)
- **Blocks**: T4
- **Blocked By**: T1
**References**:
- `apps/server-core/internal/heartbeat/redis.go:57-84` — Current SyncToDB implementation
- `apps/server-core/internal/heartbeat/redis.go:14-17` — Manager struct (needs wgMgr field)
- `apps/server-core/main.go:216-219` — HeartbeatManager creation (needs wgMgr param)
- `apps/server-core/api/devices.go:35-60` — Device List handler (reads is_active from DB)
- `apps/dashboard-ui/src/views/Devices.vue` — Dashboard reads IsActive from API response
**Acceptance Criteria**:
- [x] `go build -tags dev ./...` passes
- [x] Device with active WG peer → `is_active=true` in DB after SyncToDB cycle
- [x] Device without WG handshake → `is_active=false`
- [x] Redis heartbeat recording still works (existing device-agents unaffected)
**QA Scenarios**:
```
Scenario: Device with active WG peer shows online
Tool: Bash (curl + ssh)
Preconditions: Server running, device gogo2 connected via WG
Steps:
1. SSH to server, get token: TOKEN=$(curl -s http://localhost:8080/api/v1/auth/login -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
2. Wait 35s for SyncToDB cycle
3. curl -s http://localhost:8080/api/v1/devices -H "Authorization: Bearer $TOKEN" | python3 -c "import sys,json; devices=json.load(sys.stdin); [print(d['Name'], d.get('is_active')) for d in devices]"
Expected Result: gogo2 shows True (is_active=true)
Evidence: .sisyphus/evidence/task2-device-online.json
Scenario: WG peer handshake data present
Tool: Bash (ssh)
Steps:
1. SSH to server
2. wg show wg0 latest-handshakes
3. Compare timestamp with device LastHandshake in DB
Expected Result: Timestamps match (within 30s)
Evidence: .sisyphus/evidence/task2-handshake-match.json
```
**Commit**: YES (groups with T1)
- Message: `feat(heartbeat): use WireGuard handshake for device status instead of Redis`
- Files: `internal/heartbeat/redis.go`, `main.go`
- [x] 3. **nftables: add INPUT chain rules to InitNetwork()**
**What to do**:
- In `InitNetwork()` (after forward chain rules), add INPUT chain rules via nft CLI:
```
nft add rule ip nexusguard input ct state established,related accept comment "estab"
nft add rule ip nexusguard input icmp type echo-request accept comment "icmp_ping"
nft add rule ip nexusguard input ip saddr 10.172.21.0/24 tcp dport 8080 accept comment "wg_api"
nft add rule ip nexusguard input ip saddr 10.172.21.0/24 udp dport 51820 accept comment "wg_tunnel"
nft add rule ip nexusguard input ip saddr 10.172.21.0/24 drop comment "wg_isolation"
```
- Each rule must be idempotent (check comment before adding)
- Use `detectWGSubnet()` (already exists) for WG subnet detection
- Remove old manual rules on production server during deployment
**Must NOT do**:
- Don't change `policy accept` on INPUT chain
- Don't add rules to Docker's `inet filter` chains
- Don't add IPv6 rules
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES (with T1)
- **Parallel Group**: Wave 1 (with T1)
- **Blocks**: T4
- **Blocked By**: None
**References**:
- `apps/server-core/internal/firewall/nftables_linux.go:25-43` — Current InitNetwork (INPUT chain created but empty)
- `apps/server-core/internal/firewall/nftables_linux.go:46-62` — detectWGSubnet() function
- `apps/server-core/internal/firewall/nftables_linux.go:156-174` — AddInputRule/RemoveInputRule (existing pattern)
**Acceptance Criteria**:
- [x] `go build -tags dev ./...` passes
- [x] `nft list chain ip nexusguard input` shows all 5 rules after server start
- [x] Rules are idempotent (restart doesn't duplicate rules)
**QA Scenarios**:
```
Scenario: INPUT chain rules exist after restart
Tool: Bash (ssh + nft)
Preconditions: Server rebuilt with new code
Steps:
1. SSH to server
2. docker compose down && docker compose up -d --build
3. sleep 5
4. nft list chain ip nexusguard input
Expected Result: 5 rules visible (estab, icmp, wg_api, wg_tunnel, wg_isolation)
Evidence: .sisyphus/evidence/task3-input-rules.txt
Scenario: Ping from WG peer is dropped
Tool: Bash (ssh)
Steps:
1. SSH to server
2. nft list chain ip nexusguard input | grep wg_isolation
3. Count packets: nft -a list chain ip nexusguard input | grep wg_isolation
Expected Result: Drop rule exists with counter > 0
Evidence: .sisyphus/evidence/task3-ping-dropped.txt
Scenario: Docker networking unaffected
Tool: Bash (ssh + curl)
Steps:
1. curl -s -o /dev/null -w '%{http_code}' http://172.20.8.191:80
2. docker ps
Expected Result: HTTP 200, containers running
Evidence: .sisyphus/evidence/task3-docker-ok.txt
```
**Commit**: YES
- Message: `fix(nftables): add INPUT chain rules for WG peer isolation in InitNetwork`
- Files: `internal/firewall/nftables_linux.go`
- [x] 4. **Build + deploy to production**
**What to do**:
- Build server-core: `cd apps/server-core && go build -tags dev ./...`
- Build dashboard-ui: `cd apps/dashboard-ui && npm run build`
- Commit all changes
- Push to remote
- SSH to production: `git pull --recurse-submodules && docker compose down && docker compose up -d --build`
- Remove old manual nftables rules on production
**Must NOT do**:
- Don't skip build verification
- Don't push broken code
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 2
- **Blocks**: T5, F1-F4
- **Blocked By**: T2, T3
**References**:
- `connect_remote.txt` — Production server SSH details
- `docker-compose.yml` — Build configuration
**Acceptance Criteria**:
- [x] `go build -tags dev ./...` passes locally
- [x] `npm run build` passes locally
- [x] Production server running with new code
- [x] `curl http://172.20.8.191:80` returns 200
**QA Scenarios**:
```
Scenario: Production deployment successful
Tool: Bash (ssh + curl)
Steps:
1. SSH to server
2. docker logs nexus-guard-suite-server-core-1 2>&1 | tail -5
3. curl -s -o /dev/null -w '%{http_code}' http://172.20.8.191:80
4. curl -s -o /dev/null -w '%{http_code}' http://172.20.8.191:8080/api/v1/health
Expected Result: Server started, HTTP 200 for both endpoints
Evidence: .sisyphus/evidence/task4-deployment.txt
```
**Commit**: YES
- Message: `feat: firewall INPUT fix + WG handshake device status`
- Files: all changed files
- [x] 5. **Verify all acceptance criteria on production**
**What to do**:
- Run all acceptance tests from Metis review
- Verify device status (gogo2 shows online)
- Verify firewall (ping dropped, API works, Docker unaffected)
- Verify rules persist (restart server, check rules)
- Save all evidence
**Must NOT do**:
- Don't skip any acceptance test
- Don't assume — verify each criterion
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 2 (after T4)
- **Blocks**: F1-F4
- **Blocked By**: T4
**References**:
- `connect_remote.txt` — Production server SSH details
- Plan "Definition of Done" section
**Acceptance Criteria**:
- [x] All 7 acceptance tests pass
- [x] Evidence files saved
**QA Scenarios**:
```
Scenario: Full acceptance test suite
Tool: Bash (ssh + curl + nft + wg)
Steps:
1. Test 1: Device gogo2 is_active=true via API
2. Test 2: ping 10.172.21.1 from WG peer → RTO
3. Test 3: curl http://10.172.21.1:8080/api/v1/health → 200
4. Test 4: curl http://172.20.8.191:80 → 200 (Docker)
5. Test 5: nft list chain ip nexusguard input → 5 rules
6. Test 6: Restart server, verify rules persist
7. Test 7: Dashboard shows device Online
Expected Result: All 7 tests pass
Evidence: .sisyphus/evidence/task5-full-qa.txt
```
**Commit**: NO
---
## Final Verification Wave
> 4 review agents run in PARALLEL. ALL must APPROVE. Present consolidated results to user and get explicit "okay" before completing.
- [x] F1. **Plan Compliance Audit** — `oracle`
Read the plan end-to-end. For each "Must Have": verify implementation exists. For each "Must NOT Have": search codebase for forbidden patterns. Check evidence files exist. Compare deliverables against plan.
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
- [x] F2. **Code Quality Review** — `unspecified-high`
Run `go build -tags dev ./...` + `go vet ./...`. Review all changed files for: empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction.
Output: `Build [PASS/FAIL] | Vet [PASS/FAIL] | Files [N clean/N issues] | VERDICT`
- [x] F3. **Real Manual QA** — `unspecified-high`
Start from clean state. Execute EVERY QA scenario from EVERY task. Test cross-task integration. Test edge cases. Save to `.sisyphus/evidence/final-qa/`.
Output: `Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT`
- [x] F4. **Scope Fidelity Check** — `deep`
For each task: read "What to do", read actual diff. Verify 1:1. Check "Must NOT do" compliance. Flag unaccounted changes.
Output: `Tasks [N/N compliant] | Unaccounted [CLEAN/N files] | VERDICT`
---
## Commit Strategy
- Commit #1: Backend — wgmanager + heartbeat SyncToDB
- Commit #2: Backend — nftables INPUT chain rules
- Commit #3: Root — submodule refs
## Success Criteria
### Verification Commands
```bash
go build -tags dev ./... # Expected: no errors
go vet ./... # Expected: no warnings
wg show wg0 latest-handshakes # Expected: Unix timestamps
curl -s http://localhost:8080/api/v1/devices | jq '.[].is_active' # Expected: true for connected devices
nft list chain ip nexusguard input # Expected: rules with icmp, established, drop
```
### Final Checklist
- [x] All "Must Have" present
- [x] All "Must NOT Have" absent
- [x] Device with active WG peer shows Online
- [x] Firewall INPUT chain drops non-allowed WG traffic
- [x] Rules persist across restart
@@ -0,0 +1,689 @@
# NexusGuard Config Architecture
## TL;DR
> Unified config system for non-Docker deployment: `/etc/nexusguard/nexusguard.conf` (shell-sourceable), install/uninstall scripts, systemd service, and nginx runtime config injection for dashboard.
>
> **Deliverables**:
> - Server-core config file loader
> - Dashboard runtime config via nginx
> - Install/uninstall shell scripts
> - Systemd service file
> - Nginx config template
>
> **Estimated Effort**: Medium
> **Parallel Execution**: YES - 3 waves
> **Critical Path**: Config loader → Dashboard changes → Install scripts → Testing
---
## Context
### Original Request
User wants unified config architecture for non-Docker deployment. Currently config is split:
- Server-core: env vars from docker-compose `.env`
- Dashboard: `VITE_API_BASE_URL` baked at build time
- Device-agent: CLI args (no change needed)
### Interview Summary
**Key Discussions**:
- Config format: Shell-sourceable (export KEY=VALUE)
- Config location: `/etc/nexusguard/nexusguard.conf`
- Structure: Server-core + dashboard-ui share ONE config file
- Device-agent: No config file (uses CLI args)
- Dashboard: Runtime config via nginx template (window.__CONFIG__)
- Docker: Keep env vars unchanged
- Non-Docker: Read from nexusguard.conf
- Install: Shell script (nexusguard-install.sh)
- Uninstall: Shell script (nexusguard-uninstall.sh)
- Systemd: Service file for server-core
**Research Findings**:
- Server-core config loading: `internal/config/config.go` reads env vars via `os.Getenv()`
- Dashboard config: `src/services/api.ts` uses `import.meta.env.VITE_API_BASE_URL`
- Existing patterns: Device-agent's `install_agent.sh` and `sys-bridge.service`
- Real server: 172.20.8.191 for testing
### Metis Review
**Identified Gaps** (addressed):
- Config file path should be overridable via `NEXUSGUARD_CONF` env var
- nginx can't source bash files → use `envsubst` with template
- Secrets in conf file need `chmod 600` permissions
- Config loading order: conf file → env vars → defaults
- Install script must be idempotent
- Dashboard `window.__CONFIG__` timing: script tag MUST appear before Vue bundle
- Edge cases: empty values, spaces, special characters, BOM, Windows line endings
---
## Work Objectives
### Core Objective
Implement unified config system for non-Docker NexusGuard deployment with `/etc/nexusguard/nexusguard.conf`, install/uninstall scripts, and nginx runtime config injection.
### Concrete Deliverables
- `apps/server-core/internal/config/config_loader.go` - Config file loader
- `apps/server-core/internal/config/config_test.go` - Unit tests
- `apps/server-core/main.go` - Updated to call config loader
- `apps/dashboard-ui/src/services/api.ts` - Runtime config support
- `apps/dashboard-ui/nginx.conf.template` - Nginx config template
- `nexusguard-install.sh` - Install script
- `nexusguard-uninstall.sh` - Uninstall script
- `apps/server-core/nexusguard-server.service` - Systemd service file
### Definition of Done
- [ ] Server-core reads config from `/etc/nexusguard/nexusguard.conf`
- [ ] Dashboard reads runtime config from nginx-injected `window.__CONFIG__`
- [ ] Install script copies binaries, creates config, sets up systemd, configures nginx
- [ ] Uninstall script stops service, removes files, reloads nginx
- [ ] All unit tests pass
- [ ] Tested on real server (172.20.8.191)
### Must Have
- Config file loading with fallback to env vars
- Dashboard runtime config injection via nginx
- Idempotent install/uninstall scripts
- Secure config file permissions (chmod 600)
- Systemd service with restart on failure
### Must NOT Have (Guardrails)
- **NEVER** modify Docker behavior (docker-compose.yml, Dockerfiles stay unchanged)
- **NEVER** touch device-agent (uses CLI args, not config file)
- **NEVER** add PostgreSQL/Redis installation to install script
- **NEVER** add TLS/HTTPS setup to install script
- **NEVER** add auto-reload on config changes
- **NEVER** add config file validation (schema)
- **NEVER** add log rotation or monitoring
- **NEVER** add multi-server deployment support
- **NEVER** add `nexusguard-ctl` management CLI
---
## Verification Strategy
> **ZERO HUMAN INTERVENTION** - ALL verification is agent-executed. No exceptions.
### Test Decision
- **Infrastructure exists**: YES (Go tests for server-core, npm for dashboard)
- **Automated tests**: YES (Tests-after)
- **Framework**: Go testing (server-core), no framework for dashboard (manual verification)
### QA Policy
Every task MUST include agent-executed QA scenarios.
Evidence saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.{ext}`.
- **Go code**: Use `go test` - Run tests, assert pass
- **Shell scripts**: Use `bash` - Run script, verify exit code and file creation
- **Config loading**: Use Go test with temp files
- **Nginx config**: Use `nginx -t` to validate syntax
- **Real server**: SSH to 172.20.8.191 and test
---
## Execution Strategy
### Parallel Execution Waves
```
Wave 1 (Start Immediately - foundation):
├── Task 1: Config file loader for server-core [deep]
├── Task 2: Dashboard runtime config support [quick]
└── Task 3: Nginx config template [quick]
Wave 2 (After Wave 1 - scripts):
├── Task 4: Install script (depends: 1, 2, 3) [deep]
├── Task 5: Uninstall script (depends: 4) [quick]
└── Task 6: Systemd service file (depends: 1) [quick]
Wave FINAL (After ALL tasks):
├── Task F1: Plan compliance audit (oracle)
├── Task F2: Code quality review (unspecified-high)
├── Task F3: Real manual QA on server 172.20.8.191 (unspecified-high)
└── Task F4: Scope fidelity check (deep)
```
### Dependency Matrix
| Task | Depends On | Blocks |
|------|-----------|--------|
| 1 | None | 4, 6 |
| 2 | None | 4 |
| 3 | None | 4 |
| 4 | 1, 2, 3 | F1-F4 |
| 5 | 4 | F1-F4 |
| 6 | 1 | F1-F4 |
### Agent Dispatch Summary
- **Wave 1**: 3 tasks - T1 → `deep`, T2 → `quick`, T3 → `quick`
- **Wave 2**: 3 tasks - T4 → `deep`, T5 → `quick`, T6 → `quick`
- **FINAL**: 4 tasks - F1 → `oracle`, F2 → `unspecified-high`, F3 → `unspecified-high`, F4 → `deep`
---
## TODOs
- [x] 1. Config file loader for server-core
**What to do**:
- Create `apps/server-core/internal/config/config_loader.go`
- Implement `LoadConfFile(path string)` function
- Parse shell-sourceable format (export KEY=VALUE)
- Handle edge cases: comments (#), empty lines, spaces, Windows line endings (\r\n), BOM
- Skip malformed lines (no = sign)
- Trim whitespace around keys and values
- Call `os.Setenv()` for each valid key
- Return error if file doesn't exist (but don't fatal)
- Support path override via `NEXUSGUARD_CONF` env var
- Default path: `/etc/nexusguard/nexusguard.conf`
**Must NOT do**:
- Don't modify existing `config.Load()` function
- Don't add external dependencies (use stdlib only)
- Don't make config loading fatal (log warning if file missing)
**Recommended Agent Profile**:
- **Category**: `deep`
- **Skills**: []
- **Reason**: Go code, requires understanding of existing config pattern
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 2, 3)
- **Blocks**: Tasks 4, 6
- **Blocked By**: None
**References**:
- `apps/server-core/internal/config/config.go:25-55` - Existing config loading pattern
- `apps/server-core/main.go:176` - Where config.Load() is called
- `apps/device-agent/scripts/install_agent.sh` - Shell script pattern to follow
**Acceptance Criteria**:
- [ ] File created: `apps/server-core/internal/config/config_loader.go`
- [ ] Function `LoadConfFile(path string) error` exists
- [ ] Parses `export KEY=VALUE` format
- [ ] Skips comments (#) and empty lines
- [ ] Handles Windows line endings (\r\n)
- [ ] Trims whitespace around keys and values
- [ ] Calls `os.Setenv()` for each valid key
- [ ] Returns error for missing file (non-fatal)
- [ ] Supports `NEXUSGUARD_CONF` env var override
**QA Scenarios**:
```
Scenario: Parse valid config file
Tool: Bash (go test)
Preconditions: None
Steps:
1. Create temp file with: export JWT_SECRET=test123\nexport SERVER_SALT=salt456\nexport DB_HOST=customhost
2. Call LoadConfFile(tempPath)
3. Assert os.Getenv("JWT_SECRET") == "test123"
4. Assert os.Getenv("DB_HOST") == "customhost"
Expected Result: All values set correctly
Evidence: .sisyphus/evidence/task-1-parse-valid.txt
Scenario: Handle missing config file
Tool: Bash (go test)
Preconditions: None
Steps:
1. Call LoadConfFile("/nonexistent/path")
2. Assert error is returned
3. Assert no panic or fatal
Expected Result: Error returned, process continues
Evidence: .sisyphus/evidence/task-1-missing-file.txt
Scenario: Skip malformed lines
Tool: Bash (go test)
Preconditions: None
Steps:
1. Create temp file with: export VALID=yes\nINVALID_LINE\nexport ALSO_VALID=ok
2. Call LoadConfFile(tempPath)
3. Assert os.Getenv("VALID") == "yes"
4. Assert os.Getenv("ALSO_VALID") == "ok"
Expected Result: Malformed line skipped
Evidence: .sisyphus/evidence/task-1-malformed-lines.txt
Scenario: Handle Windows line endings
Tool: Bash (go test)
Preconditions: None
Steps:
1. Create temp file with: export KEY1=val1\r\nexport KEY2=val2\r\n
2. Call LoadConfFile(tempPath)
3. Assert os.Getenv("KEY1") == "val1"
4. Assert os.Getenv("KEY2") == "val2"
Expected Result: \r\n handled correctly
Evidence: .sisyphus/evidence/task-1-windows-endings.txt
```
**Commit**: YES
- Message: `feat(server-core): add config file loader for /etc/nexusguard/nexusguard.conf`
- Files: `apps/server-core/internal/config/config_loader.go`
- Pre-commit: `go test ./internal/config/... -v`
---
- [x] 2. Dashboard runtime config support
**What to do**:
- Modify `apps/dashboard-ui/src/services/api.ts`
- Add TypeScript type declaration for `window.__CONFIG__`
- Read `window.__CONFIG__?.apiBaseUrl` with fallback to `import.meta.env.VITE_API_BASE_URL`
- Keep Docker compatibility (VITE_API_BASE_URL still works)
- Add comment explaining runtime config injection
**Must NOT do**:
- Don't remove VITE_API_BASE_URL support (Docker compatibility)
- Don't change build process
- Don't add external dependencies
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
- **Reason**: Simple TypeScript change, single file
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 1, 3)
- **Blocks**: Task 4
- **Blocked By**: None
**References**:
- `apps/dashboard-ui/src/services/api.ts:1-10` - Current API client setup
- `apps/dashboard-ui/nginx.conf` - Current nginx config
**Acceptance Criteria**:
- [ ] File modified: `apps/dashboard-ui/src/services/api.ts`
- [ ] `window.__CONFIG__` type declared
- [ ] Runtime config read with fallback to VITE_API_BASE_URL
- [ ] TypeScript compiles without errors
**QA Scenarios**:
```
Scenario: Runtime config override
Tool: Bash (manual verification)
Preconditions: None
Steps:
1. Read api.ts file
2. Verify window.__CONFIG__?.apiBaseUrl is checked first
3. Verify fallback to import.meta.env.VITE_API_BASE_URL
Expected Result: Runtime config takes precedence
Evidence: .sisyphus/evidence/task-2-runtime-config.txt
Scenario: Docker compatibility
Tool: Bash (manual verification)
Preconditions: None
Steps:
1. Read api.ts file
2. Verify VITE_API_BASE_URL fallback exists
Expected Result: Docker builds still work
Evidence: .sisyphus/evidence/task-2-docker-compat.txt
```
**Commit**: YES (groups with Task 1)
- Message: `feat(dashboard): add runtime config support via window.__CONFIG__`
- Files: `apps/dashboard-ui/src/services/api.ts`
---
- [x] 3. Nginx config template
**What to do**:
- Create `apps/dashboard-ui/nginx.conf.template`
- Use `envsubst` placeholders for runtime config injection
- Proxy `/api/` to `http://127.0.0.1:${API_PORT}`
- Serve static SPA from `/usr/share/nexusguard/dashboard`
- Inject `window.__CONFIG__` script tag before Vue bundle
- Handle SPA fallback (try_files $uri $uri/ /index.html)
- Listen on port 80 (or configurable)
**Must NOT do**:
- Don't add TLS/HTTPS configuration
- Don't hardcode API_PORT (use envsubst)
- Don't modify existing Docker nginx.conf
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
- **Reason**: Simple nginx config template
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1 (with Tasks 1, 2)
- **Blocks**: Task 4
- **Blocked By**: None
**References**:
- `apps/dashboard-ui/nginx.conf` - Current Docker nginx config
- `apps/server-core/main.go:356` - API port default (8080)
**Acceptance Criteria**:
- [ ] File created: `apps/dashboard-ui/nginx.conf.template`
- [ ] `envsubst` placeholders for `${API_PORT}`, `${API_BASE_URL}`
- [ ] `window.__CONFIG__` injection via `sub_filter` or template
- [ ] SPA fallback configured
- [ ] `nginx -t` validates syntax
**QA Scenarios**:
```
Scenario: Nginx config syntax
Tool: Bash
Preconditions: nginx installed
Steps:
1. Run: nginx -t -c /path/to/nginx.conf.template
2. Assert exit code 0
Expected Result: Config syntax valid
Evidence: .sisyphus/evidence/task-3-nginx-syntax.txt
Scenario: Runtime config injection
Tool: Bash (manual verification)
Preconditions: None
Steps:
1. Read nginx.conf.template
2. Verify window.__CONFIG__ injection mechanism exists
3. Verify it appears before Vue bundle script
Expected Result: Config injected correctly
Evidence: .sisyphus/evidence/task-3-config-injection.txt
```
**Commit**: YES (groups with Tasks 1, 2)
- Message: `feat(dashboard): add nginx config template for runtime config injection`
- Files: `apps/dashboard-ui/nginx.conf.template`
---
- [x] 4. Install script
**What to do**:
- Create `nexusguard-install.sh` at project root
- Parse arguments (--help, --server-port, --web-port)
- Check dependencies (nginx, systemctl)
- Copy server-core binary to `/usr/local/bin/`
- Copy dashboard dist to `/usr/share/nexusguard/dashboard/`
- Create `/etc/nexusguard/nexusguard.conf` with template values
- Set permissions: `chmod 600 /etc/nexusguard/nexusguard.conf`
- Create systemd service file at `/etc/systemd/system/nexusguard-server.service`
- Create nginx config at `/etc/nginx/conf.d/nexusguard.conf`
- Enable and start service
- Print access URL
- Handle idempotency (check existing service, skip or overwrite gracefully)
**Must NOT do**:
- Don't install PostgreSQL or Redis
- Don't install nginx (assume pre-installed)
- Don't configure TLS/HTTPS
- Don't build from source (expect pre-built binaries)
- Don't modify Docker behavior
**Recommended Agent Profile**:
- **Category**: `deep`
- **Skills**: []
- **Reason**: Complex shell script with multiple system interactions
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 2 (sequential after Wave 1)
- **Blocks**: Tasks 5, F1-F4
- **Blocked By**: Tasks 1, 2, 3
**References**:
- `apps/device-agent/scripts/install_agent.sh` - Pattern to follow
- `apps/server-core/nexusguard-server.service` - Systemd template
- `apps/dashboard-ui/nginx.conf.template` - Nginx config to copy
**Acceptance Criteria**:
- [ ] File created: `nexusguard-install.sh`
- [ ] `--help` flag shows usage
- [ ] Creates `/etc/nexusguard/nexusguard.conf` with chmod 600
- [ ] Creates systemd service file
- [ ] Creates nginx config
- [ ] Enables and starts service
- [ ] Idempotent (safe to run twice)
**QA Scenarios**:
```
Scenario: Install --help
Tool: Bash
Preconditions: None
Steps:
1. Run: bash nexusguard-install.sh --help
2. Assert exit code 0
3. Assert usage text displayed
Expected Result: Help text shown
Evidence: .sisyphus/evidence/task-4-install-help.txt
Scenario: Install creates config file
Tool: Bash
Preconditions: None
Steps:
1. Run: bash nexusguard-install.sh
2. Assert /etc/nexusguard/nexusguard.conf exists
3. Assert file permissions are 600
4. Assert file contains export statements
Expected Result: Config file created securely
Evidence: .sisyphus/evidence/task-4-config-created.txt
Scenario: Install creates systemd service
Tool: Bash
Preconditions: None
Steps:
1. Run: bash nexusguard-install.sh
2. Assert /etc/systemd/system/nexusguard-server.service exists
3. Assert service is enabled
Expected Result: Systemd service configured
Evidence: .sisyphus/evidence/task-4-systemd-created.txt
Scenario: Install idempotency
Tool: Bash
Preconditions: None
Steps:
1. Run: bash nexusguard-install.sh
2. Run: bash nexusguard-install.sh (again)
3. Assert no errors
4. Assert service still running
Expected Result: Safe to run multiple times
Evidence: .sisyphus/evidence/task-4-idempotency.txt
```
**Commit**: YES
- Message: `feat: add nexusguard-install.sh for non-Docker deployment`
- Files: `nexusguard-install.sh`
- Pre-commit: `bash nexusguard-install.sh --help`
---
- [x] 5. Uninstall script
**What to do**:
- Create `nexusguard-uninstall.sh` at project root
- Stop and disable nexusguard-server service
- Remove `/usr/local/bin/nexusguard-server-core`
- Remove `/usr/share/nexusguard/dashboard/`
- Remove `/etc/nexusguard/nexusguard.conf`
- Remove `/etc/systemd/system/nexusguard-server.service`
- Remove `/etc/nginx/conf.d/nexusguard.conf`
- Reload nginx
- Reload systemd daemon
- Print confirmation message
**Must NOT do**:
- Don't remove PostgreSQL or Redis
- Don't remove database data
- Don't remove WireGuard state
- Don't remove device-agent (separate concern)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
- **Reason**: Simple cleanup script
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 2 (after Task 4)
- **Blocks**: F1-F4
- **Blocked By**: Task 4
**References**:
- `nexusguard-install.sh` - Install script to reverse
**Acceptance Criteria**:
- [ ] File created: `nexusguard-uninstall.sh`
- [ ] Stops and disables service
- [ ] Removes all installed files
- [ ] Reloads nginx and systemd
- [ ] Preserves database and WireGuard state
**QA Scenarios**:
```
Scenario: Uninstall removes files
Tool: Bash
Preconditions: Install script run first
Steps:
1. Run: bash nexusguard-uninstall.sh
2. Assert /etc/nexusguard/nexusguard.conf does not exist
3. Assert /etc/systemd/system/nexusguard-server.service does not exist
4. Assert service is stopped
Expected Result: All files removed
Evidence: .sisyphus/evidence/task-5-uninstall-removes.txt
Scenario: Uninstall preserves data
Tool: Bash
Preconditions: Install script run first
Steps:
1. Run: bash nexusguard-uninstall.sh
2. Assert PostgreSQL data still exists
3. Assert Redis data still exists
Expected Result: User data preserved
Evidence: .sisyphus/evidence/task-5-uninstall-preserves.txt
```
**Commit**: YES (groups with Task 4)
- Message: `feat: add nexusguard-uninstall.sh`
- Files: `nexusguard-uninstall.sh`
---
- [x] 6. Systemd service file
**What to do**:
- Create `apps/server-core/nexusguard-server.service`
- Use `EnvironmentFile=/etc/nexusguard/nexusguard.conf`
- Set `Restart=always` and `RestartSec=5`
- Run as root (needed for nftables/WireGuard)
- Set working directory to `/usr/local/bin`
- Add proper logging (journal)
- Add `After=network.target postgresql.service redis.service`
**Must NOT do**:
- Don't hardcode paths (use EnvironmentFile)
- Don't add Docker-specific settings
- Don't add resource limits (cgroup)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
- **Reason**: Simple systemd unit file
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 2 (with Tasks 4, 5)
- **Blocks**: F1-F4
- **Blocked By**: Task 1
**References**:
- `apps/device-agent/scripts/sys-bridge.service` - Systemd template
**Acceptance Criteria**:
- [ ] File created: `apps/server-core/nexusguard-server.service`
- [ ] `EnvironmentFile=/etc/nexusguard/nexusguard.conf`
- [ ] `Restart=always` and `RestartSec=5`
- [ ] Runs as root
- [ ] `After=network.target postgresql.service redis.service`
**QA Scenarios**:
```
Scenario: Systemd service syntax
Tool: Bash
Preconditions: systemd installed
Steps:
1. Run: systemd-analyze verify nexusguard-server.service
2. Assert exit code 0
Expected Result: Service file valid
Evidence: .sisyphus/evidence/task-6-systemd-syntax.txt
Scenario: Environment file configured
Tool: Bash
Preconditions: None
Steps:
1. Read nexusguard-server.service
2. Assert EnvironmentFile=/etc/nexusguard/nexusguard.conf exists
Expected Result: Config file path correct
Evidence: .sisyphus/evidence/task-6-env-file.txt
```
**Commit**: YES (groups with Tasks 4, 5)
- Message: `feat: add systemd service file for non-Docker deployment`
- Files: `apps/server-core/nexusguard-server.service`
---
## Final Verification Wave
- [x] F1. **Plan Compliance Audit** — `oracle`
Read the plan end-to-end. For each "Must Have": verify implementation exists. For each "Must NOT Have": search codebase for forbidden patterns. Check evidence files exist.
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
- [x] F2. **Code Quality Review** — `unspecified-high`
Run `go vet`, `go test`, `npm run build`. Review all changed files for: empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction.
Output: `Build [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT`
- [x] F3. **Real Manual QA on 172.20.8.191** — `unspecified-high`
SSH to server. Run install script. Verify config file created. Verify systemd service running. Verify nginx serving dashboard. Verify runtime config injection. Test uninstall.
Output: `Install [PASS/FAIL] | Service [RUNNING/STOPPED] | Dashboard [ACCESSIBLE/INACCESSIBLE] | VERDICT`
- [x] F4. **Scope Fidelity Check** — `deep`
For each task: read "What to do", read actual diff. Verify 1:1 — everything in spec was built, nothing beyond spec was built. Check "Must NOT do" compliance. Detect cross-task contamination.
Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT`
---
## Commit Strategy
- **Task 1**: `feat(server-core): add config file loader for /etc/nexusguard/nexusguard.conf`
- **Task 2-3**: `feat(dashboard): add runtime config support and nginx template`
- **Task 4-6**: `feat: add install/uninstall scripts and systemd service`
---
## Success Criteria
### Verification Commands
```bash
# Server-core config loading
cd apps/server-core && go test ./internal/config/... -v # Expected: PASS
# Dashboard build
cd apps/dashboard-ui && npm run build # Expected: succeeds
# Install script
bash nexusguard-install.sh --help # Expected: shows usage
# Uninstall script
bash nexusguard-uninstall.sh # Expected: removes files, stops service
# Real server test (172.20.8.191)
ssh root@172.20.8.191 "bash nexusguard-install.sh" # Expected: success
ssh root@172.20.8.191 "systemctl status nexusguard-server" # Expected: active (running)
ssh root@172.20.8.191 "curl -s http://localhost/" # Expected: HTML with window.__CONFIG__
```
### Final Checklist
- [ ] All "Must Have" present
- [ ] All "Must NOT Have" absent
- [ ] All tests pass
- [ ] Tested on real server (172.20.8.191)
@@ -0,0 +1,30 @@
# Gitea CI Build — Device Agent
**Created:** 2026-05-28
**Branch:** `dev` (demo build) + `main` (production build)
**Target:** `apps/device-agent` submodule → `nexus-device-agent` Gitea repo
## Context
Device-agent saat ini sudah punya `.gitea/workflows/build.yml` tapi belum lengkap:
- Hanya build `linux` (amd64, arm64, arm) — belum ada Windows
- Belum ada trigger untuk branch `dev` (demo build)
- Pakai GitHub Actions syntax (`softprops/action-gh-release`) yang mungkin tidak kompatibel dengan Gitea Actions
- Release job pakai `github.ref` prefix yang perlu disesuaikan
Gitea server: `ssh@172.20.8.92`
## TODOs
- [x] 1. Update `connect_remote.txt` — tambahkan Gitea build server info
- [x] 2. Rewrite `.gitea/workflows/build.yml` — trigger dev=demo, main=production, build linux+windows
- [x] 3. Verify workflow syntax — pasti Gitea Actions compatible
- [x] 4. Push ke Gitea repo dan test CI pipeline
- [x] 5. Cek build artifacts di Gitea Actions dashboard
## Final Verification Wave
- [x] F1. Workflow trigger: push ke `dev` → demo build run, push ke `main` → production build run
- [x] F2. Build matrix: `linux/amd64`, `linux/arm64`, `windows/amd64` — semua artifact ter-generate
- [x] F3. Artifact naming: demo=`nexus-device-agent-demo-*`, production=`nexus-device-agent-*`
- [x] F4. Gitea Actions dashboard menunjukkan workflow successfully completed
+9 -9
View File
@@ -16,12 +16,12 @@ This plan extends the NexusGuard architecture to include a User-Friendly Desktop
- Install Tailwind CSS v4 and matching UI dependencies (HeroIcons, Pinia).
- Configure `tauri.conf.json` to allow elevated execution (`requireAdministrator` manifest on Windows).
## Task 6.2: Sidecar Integration (Go Binary)
## Task 6.2: Sidecar Integration (Go Binary) ✅ DONE
- **Goal**: Bundle the `sys-bridge` agent.
- **Actions**:
- Modify the Phase 2 `sys-bridge` binary to support a `--json` output flag for machine-readable logs.
- Configure Tauri `externalBin` to package `sys-bridge-x86_64-pc-windows-msvc.exe` and `sys-bridge-x86_64-unknown-linux-gnu`.
- Write Rust command `start_tunnel(token: String)` that spawns the sidecar process and pipes stdout to the Vue frontend.
- Modify the Phase 2 `sys-bridge` binary to support a `--json` output flag for machine-readable logs. ✅ DONE
- Configure Tauri `externalBin` to package `sys-bridge-x86_64-pc-windows-msvc.exe` and `sys-bridge-x86_64-unknown-linux-gnu`. ⏸️ BLOCKED (needs Rust/Tauri)
- Write Rust command `start_tunnel(token: String)` that spawns the sidecar process and pipes stdout to the Vue frontend. ⏸️ BLOCKED (needs Rust/Tauri)
## Task 6.3: UI - Registration State
- **Goal**: Build the first-time setup screen.
@@ -48,8 +48,8 @@ This plan extends the NexusGuard architecture to include a User-Friendly Desktop
---
**Exit Criteria**:
- [ ] Tauri app compiles for Windows (`.msi` / `.exe`)
- [ ] App prompts for Admin rights on launch
- [ ] User can input Registration Token in GUI
- [ ] Clicking "Connect" successfully spawns the Go sidecar and establishes the WireGuard tunnel
- [ ] System Tray icon shows connection status
- [x] Tauri app compiles for Windows (`.msi` / `.exe`) — ⏸️ BLOCKED (needs Rust)
- [ ] App prompts for Admin rights on launch — ⏸️ BLOCKED (needs Rust)
- [ ] User can input Registration Token in GUI — ⏸️ BLOCKED (needs scaffold)
- [ ] Clicking "Connect" successfully spawns the Go sidecar and establishes the WireGuard tunnel — ⏸️ BLOCKED (needs Rust + scaffold)
- [ ] System Tray icon shows connection status — ⏸️ BLOCKED (needs Rust)
@@ -0,0 +1,611 @@
# Real-Time Traffic Monitoring + gRPC + TimescaleDB
## TL;DR
> Real-time device/node status via SSE + gRPC, traffic monitoring with TimescaleDB, historical charts with daily aggregation, toggle controls for chart display.
**Deliverables**:
- gRPC streaming for device-agent ↔ server (Rx/Tx data)
- SSE endpoint for dashboard real-time updates
- TimescaleDB schema for traffic logging
- Traffic recorder (Redis → DB batch)
- Dashboard traffic chart with historical data
- Toggle to disable real-time display (per device/global)
**Estimated Effort**: Large
**Parallel Execution**: YES - 4 waves
**Critical Path**: T1 → T2 → T3 → T4 → T5 → T6
---
## Context
### Original Request
User wants real-time device/node online status without page refresh, Rx/Tx traffic with charts, daily/historical logging, and toggle controls for chart display. System scales to 1000+ devices.
### Architecture Decision
- **Device-Agent → Server**: gRPC streaming (bidirectional, efficient for 1000+ connections)
- **Dashboard ← Server**: SSE (browser native, HTTP friendly, auto-reconnect)
- **Real-time state**: Redis (fast in-memory, pub/sub)
- **Traffic recording**: TimescaleDB (PostgreSQL extension, time-series optimized)
- **Historical query**: TimescaleDB continuous aggregates
### Two Device Types
1. **Device-Agent**: Custom Go agent, sends Rx/Tx via gRPC stream
2. **WireGuard Client**: Official WG clients (MikroTik, phone), data from kernel (`wg show`)
---
## Work Objectives
### Core Objective
Real-time device status + traffic monitoring for 1000+ devices with historical charts.
### Must Have
- gRPC streaming for agent traffic data
- SSE for dashboard real-time updates
- TimescaleDB for traffic logging
- Traffic chart per device/node
- Toggle to disable chart display
- Historical data query (daily/hourly)
### Must NOT Have
- Do NOT remove existing heartbeat system
- Do NOT change existing API endpoints
- Do NOT add external dependencies (Kafka, RabbitMQ)
- Do NOT use WebSocket (SSE is sufficient for dashboard)
---
## Verification Strategy
### Test Decision
- **Infrastructure exists**: YES (Go, Vue 3, PostgreSQL)
- **Automated tests**: Tests-after
- **Framework**: Go test + npm test
---
## Execution Strategy
### Parallel Execution Waves
```
Wave 1 (Foundation):
├── T1: TimescaleDB schema + migration
├── T2: gRPC proto definition
└── T3: Traffic recorder (Redis → DB)
Wave 2 (Backend):
├── T4: gRPC server implementation
├── T5: SSE endpoint
└── T6: Traffic query API
Wave 3 (Agent):
├── T7: Device-agent gRPC client
└── T8: Kernel sync enhancement
Wave 4 (Frontend):
├── T9: Dashboard traffic chart
├── T10: Toggle controls
└── T11: Historical data view
```
---
## TODOs
- [ ] 1. **TimescaleDB schema + migration**
**What to do**:
- Create migration file `apps/server-core/migrations/003_timescaledb_traffic.sql`
- Create `device_traffic` table:
```sql
CREATE TABLE device_traffic (
time TIMESTAMPTZ NOT NULL DEFAULT NOW(),
device_id UUID NOT NULL,
node_id UUID,
rx_bytes BIGINT DEFAULT 0,
tx_bytes BIGINT DEFAULT 0,
rx_rate BIGINT DEFAULT 0,
tx_rate BIGINT DEFAULT 0
);
SELECT create_hypertable('device_traffic', 'time');
```
- Create daily aggregate view:
```sql
CREATE MATERIALIZED VIEW device_traffic_daily
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 day', time) AS day,
device_id,
MAX(rx_bytes) - MIN(rx_bytes) AS rx_total,
MAX(tx_bytes) - MIN(tx_bytes) AS tx_total
FROM device_traffic
GROUP BY day, device_id;
```
- Create hourly aggregate view
- Add retention policy (90 days raw, 1 year aggregated)
- Add indexes on device_id + time
**Must NOT do**:
- Do NOT remove existing tables
- Do NOT change existing schema
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: YES (with T2, T3)
- **Parallel Group**: Wave 1
- **Blocks**: T6
- **Blocked By**: None
**References**:
- `apps/server-core/migrations/` - existing migration pattern
- TimescaleDB docs: https://docs.timescale.com/
**Acceptance Criteria**:
- [ ] `go build -tags dev ./...` passes
- [ ] Migration runs without errors
- [ ] Tables and views created
**Commit**: YES
- Message: `feat(db): add TimescaleDB schema for traffic monitoring`
- Files: `apps/server-core/migrations/003_timescaledb_traffic.sql`
- [ ] 2. **gRPC proto definition**
**What to do**:
- Create `apps/server-core/proto/traffic.proto`:
```protobuf
syntax = "proto3";
package nexusguard.traffic;
service TrafficService {
rpc StreamTraffic(stream TrafficReport) returns (stream TrafficCommand);
rpc ReportTraffic(TrafficReport) returns (TrafficAck);
}
message TrafficReport {
string device_id = 1;
int64 rx_bytes = 2;
int64 tx_bytes = 3;
int64 timestamp = 4;
}
message TrafficCommand {
string command = 1;
string target = 2;
}
message TrafficAck {
bool success = 1;
string message = 2;
}
```
- Generate Go code: `protoc --go_out=. --go-grpc_out=. traffic.proto`
- Generate TypeScript types for frontend (optional)
**Must NOT do**:
- Do NOT include sensitive data in proto
- Do NOT add authentication in proto (handle at interceptors)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: YES (with T1, T3)
- **Parallel Group**: Wave 1
- **Blocks**: T4, T7
- **Blocked By**: None
**References**:
- gRPC Go docs: https://grpc.io/docs/languages/go/
- Protobuf docs: https://protobuf.dev/
**Acceptance Criteria**:
- [ ] Proto file compiles without errors
- [ ] Generated Go code exists
- [ ] Generated TypeScript types exist
**Commit**: YES
- Message: `feat(grpc): add traffic proto definition`
- Files: `apps/server-core/proto/traffic.proto`, generated files
- [ ] 3. **Traffic recorder (Redis → DB batch)**
**What to do**:
- Create `apps/server-core/internal/traffic/recorder.go`:
- `TrafficRecorder` struct with Redis client + TimescaleDB connection
- `Record(deviceID, rxBytes, txBytes int64)` - stores to Redis (fast)
- `StartBatchSync(ctx, interval)` - goroutine that batch inserts to DB every 60s
- `GetDeviceTraffic(deviceID, from, to time.Time)` - query historical data
- `GetNodeTraffic(nodeID, from, to time.Time)` - aggregate per node
- Redis key format: `traffic:{device_id}:{timestamp}`
- Batch insert: collect from Redis, insert to TimescaleDB, delete from Redis
- Handle zero-value timestamps
- Thread-safe with sync.Mutex
**Must NOT do**:
- Do NOT block on Redis write
- Do NOT lose data on server restart (Redis persistence)
- Do NOT query DB on every traffic report
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: YES (with T1, T2)
- **Parallel Group**: Wave 1
- **Blocks**: T4, T5, T6
- **Blocked By**: None
**References**:
- `apps/server-core/internal/heartbeat/redis.go` - Redis pattern
- TimescaleDB insert pattern
**Acceptance Criteria**:
- [ ] `go build -tags dev ./...` passes
- [ ] Traffic recorded to Redis on Report()
- [ ] Batch sync inserts to TimescaleDB
**Commit**: YES
- Message: `feat(traffic): add Redis → TimescaleDB recorder`
- Files: `apps/server-core/internal/traffic/recorder.go`
- [ ] 4. **gRPC server implementation**
**What to do**:
- Create `apps/server-core/api/grpc_server.go`:
- Implement `TrafficServiceServer` interface
- `StreamTraffic`: bidirectional streaming
- Receive `TrafficReport` from agent
- Call `recorder.Record()`
- Send `TrafficCommand` if needed (e.g., rate limit)
- `ReportTraffic`: unary call for simple reports
- Connection management: track active agents
- Graceful shutdown
- Add gRPC server to main.go (separate port, e.g., 8081)
- Add TLS support (optional, for production)
**Must NOT do**:
- Do NOT expose gRPC to public internet (internal only)
- Do NOT change existing HTTP API
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: NO (depends on T2, T3)
- **Parallel Group**: Wave 2
- **Blocks**: T7
- **Blocked By**: T2, T3
**References**:
- `apps/server-core/main.go` - server startup pattern
- gRPC Go server examples
**Acceptance Criteria**:
- [ ] `go build -tags dev ./...` passes
- [ ] gRPC server starts on port 8081
- [ ] StreamTraffic accepts connections
**Commit**: YES
- Message: `feat(grpc): implement traffic streaming server`
- Files: `apps/server-core/api/grpc_server.go`, `apps/server-core/main.go`
- [ ] 5. **SSE endpoint**
**What to do**:
- Create `apps/server-core/api/sse.go`:
- `SSEHandler` struct with Redis + recorder
- `StreamStatus(c *gin.Context)` - SSE endpoint
- Register client in Redis pub/sub channel
- Push device status updates (online/offline, Rx/Tx rates)
- Handle client disconnect (cleanup)
- Heartbeat ping every 30s (keep connection alive)
- Register route: `GET /api/v1/devices/stream`
- Use Redis pub/sub for multi-instance support
**Must NOT do**:
- Do NOT block on SSE write
- Do NOT store SSE clients in memory (use Redis pub/sub)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: NO (depends on T3)
- **Parallel Group**: Wave 2
- **Blocks**: T9
- **Blocked By**: T3
**References**:
- SSE spec: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events
- Redis pub/sub pattern
**Acceptance Criteria**:
- [ ] `go build -tags dev ./...` passes
- [ ] `curl -N http://localhost:8080/api/v1/devices/stream` returns SSE stream
- [ ] Status updates pushed on device changes
**Commit**: YES
- Message: `feat(sse): add device status streaming endpoint`
- Files: `apps/server-core/api/sse.go`, `apps/server-core/main.go`
- [ ] 6. **Traffic query API**
**What to do**:
- Add endpoints to `apps/server-core/api/traffic.go`:
- `GET /api/v1/devices/:id/traffic?from=&to=` - device traffic history
- `GET /api/v1/nodes/:id/traffic?from=&to=` - node aggregate traffic
- `GET /api/v1/traffic/summary` - today's summary (all devices)
- Query TimescaleDB with time bucket aggregation
- Return JSON with timestamps + values for chart
- Support different granularities: minute, hour, day
**Must NOT do**:
- Do NOT expose raw traffic data (use aggregates)
- Do NOT allow querying beyond retention period
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: NO (depends on T1, T3)
- **Parallel Group**: Wave 2
- **Blocks**: T9, T11
- **Blocked By**: T1, T3
**References**:
- `apps/server-core/api/devices.go` - API pattern
- TimescaleDB time_bucket queries
**Acceptance Criteria**:
- [ ] `go build -tags dev ./...` passes
- [ ] GET /api/v1/devices/:id/traffic returns data
- [ ] Response format suitable for charts
**Commit**: YES
- Message: `feat(api): add traffic query endpoints`
- Files: `apps/server-core/api/traffic.go`, `apps/server-core/main.go`
- [ ] 7. **Device-agent gRPC client**
**What to do**:
- Modify `apps/device-agent/main.go`:
- Add gRPC client connection to server (port 8081)
- Periodic traffic report (every 5 seconds):
- Read Rx/Tx from WireGuard interface
- Send `TrafficReport` via gRPC stream
- Handle server commands (rate limit, disconnect)
- Reconnect on connection loss
- Add `--grpc-port` flag (default 8081)
- Add traffic collection from WireGuard kernel
**Must NOT do**:
- Do NOT break existing heartbeat system
- Do NOT add new dependencies (use existing wgctrl)
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: NO (depends on T2, T4)
- **Parallel Group**: Wave 3
- **Blocks**: None
- **Blocked By**: T2, T4
**References**:
- `apps/device-agent/main.go` - agent entry point
- `apps/device-agent/internal/tunnel/wireguard.go` - WG interface access
**Acceptance Criteria**:
- [ ] `go build` passes
- [ ] Agent connects to gRPC server
- [ ] Traffic reports sent every 5 seconds
**Commit**: YES
- Message: `feat(agent): add gRPC traffic streaming client`
- Files: `apps/device-agent/main.go`, new gRPC client code
- [ ] 8. **Kernel sync enhancement**
**What to do**:
- Enhance `apps/server-core/internal/wgmanager/handshakesync.go`:
- Add Rx/Tx bytes collection per peer
- Store traffic data to Redis/TimescaleDB
- Handle WireGuard client devices (no agent)
- Update `GetPeerHandshakes()` to include traffic data
- Ensure kernel sync records traffic for all WG clients
**Must NOT do**:
- Do NOT change existing handshake logic
- Do NOT break agent devices
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: NO (depends on T3)
- **Parallel Group**: Wave 3
- **Blocks**: None
- **Blocked By**: T3
**References**:
- `apps/server-core/internal/wgmanager/handshakesync.go` - existing sync
**Acceptance Criteria**:
- [ ] `go build -tags dev ./...` passes
- [ ] Kernel sync records Rx/Tx for WG clients
**Commit**: YES
- Message: `feat(wgmanager): enhance kernel sync with traffic data`
- Files: `apps/server-core/internal/wgmanager/handshakesync.go`
- [ ] 9. **Dashboard traffic chart**
**What to do**:
- Create `apps/dashboard-ui/src/components/TrafficChart.vue`:
- Line chart for Rx/Tx over time
- Use Chart.js or ApexCharts
- Real-time updates via SSE
- Responsive design
- Add chart to `DeviceDetail.vue` (per-device)
- Add chart to `Dashboard.vue` (per-node aggregate)
- Time range selector (1h, 6h, 24h, 7d, 30d)
- Auto-refresh every 5 seconds
**Must NOT do**:
- Do NOT add heavy chart libraries (use lightweight)
- Do NOT block UI on chart render
**Recommended Agent Profile**:
- **Category**: `visual-engineering`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: YES (with T10, T11)
- **Parallel Group**: Wave 4
- **Blocks**: None
- **Blocked By**: T5, T6
**References**:
- `apps/dashboard-ui/src/views/DeviceDetail.vue` - existing page
- Chart.js docs: https://www.chartjs.org/
**Acceptance Criteria**:
- [ ] `npm run build` passes
- [ ] Chart displays real-time traffic
- [ ] Time range selector works
**Commit**: YES
- Message: `feat(ui): add traffic chart component`
- Files: `apps/dashboard-ui/src/components/TrafficChart.vue`, `apps/dashboard-ui/src/views/DeviceDetail.vue`, `apps/dashboard-ui/src/views/Dashboard.vue`
- [ ] 10. **Toggle controls**
**What to do**:
- Add toggle to `DeviceDetail.vue`:
- "Real-time Traffic" toggle (per device)
- When OFF: chart shows historical only, no live updates
- When ON: chart updates in real-time via SSE
- Add global toggle to `Dashboard.vue`:
- "Disable All Real-time Charts" toggle
- Saves preference to localStorage
- Backend: SSE still sends data, frontend just ignores if toggle OFF
- Traffic recording always active (toggle only affects display)
**Must NOT do**:
- Do NOT stop recording when toggle is OFF
- Do NOT add new backend endpoints
**Recommended Agent Profile**:
- **Category**: `visual-engineering`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: YES (with T9, T11)
- **Parallel Group**: Wave 4
- **Blocks**: None
- **Blocked By**: T9
**References**:
- `apps/dashboard-ui/src/views/DeviceDetail.vue`
- localStorage pattern
**Acceptance Criteria**:
- [ ] `npm run build` passes
- [ ] Per-device toggle works
- [ ] Global toggle works
- [ ] Recording continues when display is OFF
**Commit**: YES
- Message: `feat(ui): add real-time chart toggle controls`
- Files: `apps/dashboard-ui/src/components/TrafficChart.vue`, `apps/dashboard-ui/src/views/DeviceDetail.vue`, `apps/dashboard-ui/src/views/Dashboard.vue`
- [ ] 11. **Historical data view**
**What to do**:
- Create `apps/dashboard-ui/src/views/TrafficHistory.vue`:
- Full-page traffic history view
- Date range picker
- Device/node selector
- Export to CSV
- Daily/hourly aggregation
- Add route: `/traffic-history`
- Query backend traffic API
- Display in table + chart
**Must NOT do**:
- Do NOT allow querying beyond retention period
- Do NOT expose raw data (use aggregates only)
**Recommended Agent Profile**:
- **Category**: `visual-engineering`
- **Skills**: `[]`
**Parallelization**:
- **Can Run In Parallel**: YES (with T9, T10)
- **Parallel Group**: Wave 4
- **Blocks**: None
- **Blocked By**: T6
**References**:
- `apps/dashboard-ui/src/router/index.ts` - routing
- Date picker component
**Acceptance Criteria**:
- [ ] `npm run build` passes
- [ ] History page accessible at /traffic-history
- [ ] Date range filter works
- [ ] Export to CSV works
**Commit**: YES
- Message: `feat(ui): add traffic history view`
- Files: `apps/dashboard-ui/src/views/TrafficHistory.vue`, `apps/dashboard-ui/src/router/index.ts`
---
## Final Verification Wave
- [ ] F1. **Plan Compliance Audit** — `oracle`
- [ ] F2. **Code Quality Review** — `unspecified-high`
- [ ] F3. **Real Manual QA** — `unspecified-high`
- [ ] F4. **Scope Fidelity Check** — `deep`
---
## Commit Strategy
- Commit #1: Backend — TimescaleDB + gRPC proto
- Commit #2: Backend — gRPC server + SSE
- Commit #3: Agent — gRPC client
- Commit #4: Frontend — Dashboard charts
---
## Success Criteria
### Verification Commands
```bash
go build -tags dev ./... # Expected: no errors
cd apps/dashboard-ui && npm run build # Expected: no errors
psql -d nexusguard -c "SELECT * FROM device_traffic LIMIT 1" # Expected: empty or data
```
### Final Checklist
- [ ] gRPC streaming works for 1000+ connections
- [ ] SSE pushes real-time status to dashboard
- [ ] TimescaleDB stores traffic data
- [ ] Charts show real-time + historical data
- [ ] Toggle controls work (per device + global)
- [ ] Performance: <100ms latency for status updates
+82 -1
View File
@@ -7,7 +7,30 @@ echo "========================================="
STATE_FILE=".update-state"
FORCE_REBUILD=false
[ "$1" = "--force" ] && FORCE_REBUILD=true
BACKUP=false
NO_MIGRATE=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--force)
FORCE_REBUILD=true
shift
;;
--backup)
BACKUP=true
shift
;;
--no-migrate)
NO_MIGRATE=true
shift
;;
*)
echo "[!] Unknown option: $1"
exit 1
;;
esac
done
# 1. Pastikan file env ada
if [ ! -f .env ]; then
@@ -54,6 +77,57 @@ if [ "$NEEDS_REBUILD" = true ]; then
echo "[+] Sourcing environment variables from root .env..."
set -a && . .env && set +a
# Volume safety check
echo "[+] Checking PostgreSQL volume safety..."
if ! docker volume inspect pgdata >/dev/null 2>&1; then
echo "[!] WARNING: PostgreSQL volume 'pgdata' not found. It will be created on container startup."
echo "[!] If you expect existing data, check your volume configuration."
else
echo "[+] PostgreSQL volume 'pgdata' found."
fi
# Backup option
if [ "$BACKUP" = true ]; then
echo "[+] Creating backup of PostgreSQL data..."
BACKUP_DIR="./backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="$BACKUP_DIR/pgdata_backup_$TIMESTAMP.tar"
# Backup the volume using a temporary container
if docker run --rm -v pgdata:/data -v "$(pwd)/$BACKUP_DIR":/backup ubuntu tar czf "/backup/pgdata_backup_$TIMESTAMP.tar.gz" -C /data . 2>/dev/null; then
echo "[+] Backup created successfully: $BACKUP_FILE.gz"
else
echo "[!] Backup failed. Continuing without backup."
fi
fi
# Password sync if DB_PASSWORD changed
echo "[+] Checking for PostgreSQL password synchronization..."
# Get current DB_PASSWORD from .env
CURRENT_DB_PASSWORD="${DB_PASSWORD:-nexusguard}"
# Try to connect to existing postgres container and update password if needed
POSTGRES_CONTAINER=$(docker compose ps -q postgres 2>/dev/null)
if [ -n "$POSTGRES_CONTAINER" ]; then
echo "[+] Found running PostgreSQL container. Checking password..."
# Test if current password works
if docker exec "$POSTGRES_CONTAINER" pg_isready -U nexusguard -d nexusguard 2>/dev/null; then
echo "[+] Current password works. No password update needed."
else
echo "[!] Current password failed. Attempting to update PostgreSQL password to match .env..."
# Try to alter user password (requires superuser or same user)
if docker exec -u postgres "$POSTGRES_CONTAINER" psql -c "ALTER USER nexusguard WITH PASSWORD '$CURRENT_DB_PASSWORD';" 2>/dev/null; then
echo "[+] PostgreSQL password updated successfully."
else
echo "[!] Failed to update password. You may need to manually update it or check permissions."
echo "[!] Continuing update - authentication may fail if password mismatch."
fi
fi
else
echo "[i] No running PostgreSQL container found. Password will be set on container initialization (if data directory is empty)."
fi
echo "[+] Stopping existing containers..."
docker compose down
@@ -63,8 +137,12 @@ if [ "$NEEDS_REBUILD" = true ]; then
echo "[+] Restarting containers..."
docker compose up -d
if [ "$NO_MIGRATE" = false ]; then
echo "[+] Running automated database migrations..."
docker exec nexus-guard-suite-server-core-1 ./server-core -migrate-prod || echo "[!] Migration skipped or failed. It might not be needed."
else
echo "[+] Skipping database migration (--no-migrate flag used)."
fi
echo "[+] Backfilling interface addresses..."
docker exec nexus-guard-suite-server-core-1 ./server-core -backfill-interface 2>/dev/null || echo "[!] Backfill skipped or not needed."
@@ -79,6 +157,9 @@ if [ "$NEEDS_REBUILD" = true ]; then
echo " Update & Deployment complete! "
echo " Dashboard : http://localhost:${WEB_PORT:-80}"
echo " API Port : ${API_PORT:-8080}"
if [ "$BACKUP" = true ]; then
echo " Backup : Stored in ./backups/ directory"
fi
echo "========================================="
else
echo "========================================="