24 KiB
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_URLbaked 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.goreads env vars viaos.Getenv() - Dashboard config:
src/services/api.tsusesimport.meta.env.VITE_API_BASE_URL - Existing patterns: Device-agent's
install_agent.shandsys-bridge.service - Real server: 172.20.8.191 for testing
Metis Review
Identified Gaps (addressed):
- Config file path should be overridable via
NEXUSGUARD_CONFenv var - nginx can't source bash files → use
envsubstwith template - Secrets in conf file need
chmod 600permissions - 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 loaderapps/server-core/internal/config/config_test.go- Unit testsapps/server-core/main.go- Updated to call config loaderapps/dashboard-ui/src/services/api.ts- Runtime config supportapps/dashboard-ui/nginx.conf.template- Nginx config templatenexusguard-install.sh- Install scriptnexusguard-uninstall.sh- Uninstall scriptapps/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-ctlmanagement 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 -tto 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
-
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_CONFenv 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 patternapps/server-core/main.go:176- Where config.Load() is calledapps/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) errorexists - Parses
export KEY=VALUEformat - 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_CONFenv 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.txtCommit: 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
- Create
-
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__?.apiBaseUrlwith fallback toimport.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 setupapps/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.txtCommit: YES (groups with Task 1)
- Message:
feat(dashboard): add runtime config support via window.__CONFIG__ - Files:
apps/dashboard-ui/src/services/api.ts
- Modify
-
3. Nginx config template
What to do:
- Create
apps/dashboard-ui/nginx.conf.template - Use
envsubstplaceholders for runtime config injection - Proxy
/api/tohttp://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 configapps/server-core/main.go:356- API port default (8080)
Acceptance Criteria:
- File created:
apps/dashboard-ui/nginx.conf.template envsubstplaceholders for${API_PORT},${API_BASE_URL}window.__CONFIG__injection viasub_filteror template- SPA fallback configured
nginx -tvalidates 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.txtCommit: YES (groups with Tasks 1, 2)
- Message:
feat(dashboard): add nginx config template for runtime config injection - Files:
apps/dashboard-ui/nginx.conf.template
- Create
-
4. Install script
What to do:
- Create
nexusguard-install.shat 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.confwith 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 followapps/server-core/nexusguard-server.service- Systemd templateapps/dashboard-ui/nginx.conf.template- Nginx config to copy
Acceptance Criteria:
- File created:
nexusguard-install.sh --helpflag shows usage- Creates
/etc/nexusguard/nexusguard.confwith 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.txtCommit: YES
- Message:
feat: add nexusguard-install.sh for non-Docker deployment - Files:
nexusguard-install.sh - Pre-commit:
bash nexusguard-install.sh --help
- Create
-
5. Uninstall script
What to do:
- Create
nexusguard-uninstall.shat 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.txtCommit: YES (groups with Task 4)
- Message:
feat: add nexusguard-uninstall.sh - Files:
nexusguard-uninstall.sh
- Create
-
6. Systemd service file
What to do:
- Create
apps/server-core/nexusguard-server.service - Use
EnvironmentFile=/etc/nexusguard/nexusguard.conf - Set
Restart=alwaysandRestartSec=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.confRestart=alwaysandRestartSec=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.txtCommit: YES (groups with Tasks 4, 5)
- Message:
feat: add systemd service file for non-Docker deployment - Files:
apps/server-core/nexusguard-server.service
- Create
Final Verification Wave
-
F1. Plan Compliance Audit —
oracleRead 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 -
F2. Code Quality Review —
unspecified-highRungo 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 -
F3. Real Manual QA on 172.20.8.191 —
unspecified-highSSH 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 -
F4. Scope Fidelity Check —
deepFor 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
# 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)