Files
Nexus-Guard-Suite/.omo/plans/archived/nexusguard-config-architecture.md
T
datadunia cbacfea7f2
NexusGuard CI / server-core-test (push) Failing after 3m6s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 4s
NexusGuard CI / device-agent-cross-build (amd64, linux) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (amd64, windows) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (arm64, linux) (push) Has been skipped
NexusGuard CI / dashboard-test (push) Failing after 4s
NexusGuard CI / dashboard-dist (push) Has been skipped
chore: update submodule refs, clean up plans/evidence, update .gitignore
2026-06-07 23:53:15 +07:00

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_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

  • 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

  • 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

  • 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

  • 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

  • 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

  • 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

  • F1. Plan Compliance Auditoracle 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

  • F2. Code Quality Reviewunspecified-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

  • F3. Real Manual QA on 172.20.8.191unspecified-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

  • F4. Scope Fidelity Checkdeep 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

# 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)