From 566c5b9da4e15ccd1b179e79b8f586904ae0e4c4 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 14 Jun 2026 09:55:56 +0100 Subject: [PATCH] Add lab-agent hot-dev mode --- Makefile | 17 ++++++- README.md | 4 ++ .../subsystems/deployment-installability.md | 16 +++++- .../v6/internal/subsystems/monitoring.md | 51 +++++++++++-------- frontend-modern/package.json | 5 ++ .../monitoring/canonical_guardrails_test.go | 4 ++ .../monitoring/monitor_polling_containers.go | 1 + package.json | 5 ++ scripts/hot-dev-bg.sh | 16 ++++++ scripts/hot-dev.sh | 8 +++ scripts/lib/hot-dev-runtime.sh | 20 ++++++++ scripts/tests/test-hot-dev-bg.sh | 36 +++++++++++++ scripts/tests/test-hot-dev-runtime.sh | 44 ++++++++++++++++ 13 files changed, 202 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index f1d7629d89..257a2b868a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Pulse Makefile for development -.PHONY: build run dev dev-status dev-logs dev-stop dev-restart dev-backend-restart dev-verify dev-foreground frontend backend all clean distclean dev-hot lint lint-backend lint-frontend format format-backend format-frontend build-agents control-plane handoff +.PHONY: build run dev dev-lab dev-status dev-status-lab dev-logs dev-stop dev-restart dev-restart-lab dev-backend-restart dev-verify dev-verify-lab dev-foreground dev-foreground-lab frontend backend all clean distclean dev-hot lint lint-backend lint-frontend format format-backend format-frontend build-agents control-plane handoff FRONTEND_DIR := frontend-modern FRONTEND_DIST := $(FRONTEND_DIR)/dist @@ -30,9 +30,15 @@ run: build dev: npm run dev +dev-lab: + npm run dev:lab + dev-status: npm run dev:status +dev-status-lab: + npm run dev:status:lab + dev-logs: npm run dev:logs @@ -42,15 +48,24 @@ dev-stop: dev-restart: npm run dev:restart +dev-restart-lab: + npm run dev:restart:lab + dev-backend-restart: npm run dev:backend-restart dev-verify: npm run dev:verify +dev-verify-lab: + npm run dev:verify:lab + dev-foreground: npm run dev:foreground +dev-foreground-lab: + npm run dev:foreground:lab + dev-hot: npm run dev:foreground diff --git a/README.md b/README.md index 502e6ddd22..045c9ce78b 100644 --- a/README.md +++ b/README.md @@ -132,12 +132,16 @@ on startup unless you override it with `HOT_DEV_AUTH_USER` and Canonical local dev commands: - `npm run dev` — start the managed runtime and reclaim the canonical dev ports if an older unmanaged session is still using them +- `npm run dev:lab` — start the managed runtime in lab-agent mode, with the frontend/backend exposed on the LAN and Proxmox LXC Docker inventory enabled for installed lab agents - `npm run dev:status` — show frontend shell health, proxied API health, direct backend health, and listener ownership +- `npm run dev:status:lab` — show status using the same LAN-bound lab-agent defaults used by `dev:lab` - `npm run dev:verify` — run the managed browser proof pack against the live dev runtime, including runtime recovery, the Patrol blocked-runtime page contract, and the desktop Recovery layout guard while the launcher suppresses unrelated backend rebuild churn for the duration of the proof pack +- `npm run dev:verify:lab` — run the managed proof pack after applying lab-agent runtime defaults - `npm run dev:logs` — tail the managed runtime log - `npm run dev:backend-restart` — bounce only the managed backend through the launcher contract - `npm run dev:stop` — stop the managed runtime - `npm run dev:foreground` — run the foreground hot-reload launcher intentionally if you need an attached shell +- `npm run dev:foreground:lab` — run the foreground hot-reload launcher with lab-agent defaults for troubleshooting If `npm run dev:verify` passes, the managed dev shell, proxy path, backend health endpoint, browser recovery path, Patrol blocked-runtime page behavior, diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index c5ab54dedc..277bf01eff 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -388,6 +388,15 @@ TLS floor in the dynamic config. explicit `PULSE_DEV_LAN=true` opt-in; only that mode may bind Vite/backend listeners to `0.0.0.0`, include LAN origins, or advertise the detected LAN browser entrypoint. + The canonical shortcut for the installed homelab-agent case is lab-agent + mode: `PULSE_DEV_LAB_AGENTS=true` must enable LAN exposure plus the + Proxmox guest Docker detection/inventory opt-ins together, and the + repo-root wrapper surface must expose that mode through `npm run dev:lab`, + `npm run dev:restart:lab`, `npm run dev:status:lab`, + `npm run dev:verify:lab`, and `npm run dev:foreground:lab`. The frontend + workspace package and Makefile must delegate their lab-agent targets through + those same repo-root npm wrappers rather than duplicating raw launcher + commands or teaching developers to paste one-off environment strings. The hot-dev supervisor must also recover its managed PID file from a live `hot-dev-bg.sh supervise` process before treating the runtime as unmanaged. Backend health monitoring must distinguish HTTP startup grace from a missing @@ -1509,8 +1518,11 @@ route through the managed runtime control plane when they are operating on the local dev stack, instead of resurrecting lane-local `hot-dev.sh` or raw Vite process management through separate shell folklore. For Makefile targets, that means dispatching through the canonical repo-root npm wrappers (`npm run dev`, -`npm run dev:status`, `npm run dev:restart`, `npm run dev:backend-restart`, -`npm run dev:verify`, `npm run dev:stop`, and `npm run dev:foreground`) rather +`npm run dev:lab`, `npm run dev:status`, `npm run dev:status:lab`, +`npm run dev:restart`, `npm run dev:restart:lab`, +`npm run dev:backend-restart`, `npm run dev:verify`, +`npm run dev:verify:lab`, `npm run dev:stop`, `npm run dev:foreground`, and +`npm run dev:foreground:lab`) rather than shelling directly into `scripts/hot-dev-bg.sh`. When `scripts/clean-mock-alerts.sh` needs to quiesce a local dev runtime, it must stop the managed session through `hot-dev-bg` before touching legacy diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md index 65f9cc3f23..6ea2ceb476 100644 --- a/docs/release-control/v6/internal/subsystems/monitoring.md +++ b/docs/release-control/v6/internal/subsystems/monitoring.md @@ -55,25 +55,26 @@ truth for live infrastructure data. 31. `internal/monitoring/availability_poller.go` 32. `internal/monitoring/scheduler.go` 33. `internal/monitoring/docker_detection.go` -34. `internal/mock/fixture_graph.go` -35. `internal/dockeragent/docker_client.go` -36. `pkg/agents/docker/report.go` -37. `internal/models/models.go` -38. `internal/models/models_frontend.go` -39. `internal/models/converters.go` -40. `internal/models/deepcopy.go` -41. `internal/mock/generator.go` -42. `internal/mock/demo_scenarios.go` -43. `internal/kubernetesagent/agent.go` -44. `pkg/agents/kubernetes/report.go` -45. `internal/monitoring/temperature.go` -46. `internal/truenas/client.go` -47. `internal/truenas/disk_health.go` -48. `internal/truenas/provider.go` -49. `internal/models/ceph_cluster_identity.go` -50. `internal/truenas/types.go` -51. `internal/monitoring/monitor_alert_sync.go` -52. `internal/monitoring/platform_poller_shared.go` +34. `internal/monitoring/monitor_polling_containers.go` +35. `internal/mock/fixture_graph.go` +36. `internal/dockeragent/docker_client.go` +37. `pkg/agents/docker/report.go` +38. `internal/models/models.go` +39. `internal/models/models_frontend.go` +40. `internal/models/converters.go` +41. `internal/models/deepcopy.go` +42. `internal/mock/generator.go` +43. `internal/mock/demo_scenarios.go` +44. `internal/kubernetesagent/agent.go` +45. `pkg/agents/kubernetes/report.go` +46. `internal/monitoring/temperature.go` +47. `internal/truenas/client.go` +48. `internal/truenas/disk_health.go` +49. `internal/truenas/provider.go` +50. `internal/models/ceph_cluster_identity.go` +51. `internal/truenas/types.go` +52. `internal/monitoring/monitor_alert_sync.go` +53. `internal/monitoring/platform_poller_shared.go` ## Shared Boundaries @@ -162,9 +163,15 @@ truth for live infrastructure data. host agent remains one hybrid top-level system across rebuild ticks. 14. Add or change Proxmox-side LXC Docker detection or inventory through `internal/monitoring/docker_detection.go`, - `internal/monitoring/monitor_pve_guest_poll.go`, and monitoring guardrails - together. Socket detection may only annotate LXC guests after explicit - server opt-in. LXC Docker inventory may only emit Docker / Podman + `internal/monitoring/monitor_pve_guest_poll.go`, + `internal/monitoring/monitor_polling_containers.go`, and monitoring + guardrails together. Socket detection may only annotate LXC guests after + explicit server opt-in. Both the efficient cluster/resources guest poll and + the traditional per-node container polling path must run + `CollectProxmoxGuestDockerInventory` after Docker presence detection and + before updating container state, so the Docker runtime lens does not depend + on which Proxmox polling path is active. LXC Docker inventory may only emit + Docker / Podman module-compatible reports into `ApplyDockerReport`, must skip guests with a linked online guest-local host agent, and must keep the command set to minimal read-only Docker summary and aggregate stats collection. The socket diff --git a/frontend-modern/package.json b/frontend-modern/package.json index ebd34d507e..0cd2f1d73a 100644 --- a/frontend-modern/package.json +++ b/frontend-modern/package.json @@ -15,13 +15,18 @@ "homepage": "https://github.com/rcourtman/Pulse", "scripts": { "dev": "npm --prefix .. run dev", + "dev:lab": "npm --prefix .. run dev:lab", "dev:logs": "npm --prefix .. run dev:logs", "dev:restart": "npm --prefix .. run dev:restart", + "dev:restart:lab": "npm --prefix .. run dev:restart:lab", "dev:backend-restart": "npm --prefix .. run dev:backend-restart", "dev:status": "npm --prefix .. run dev:status", + "dev:status:lab": "npm --prefix .. run dev:status:lab", "dev:stop": "npm --prefix .. run dev:stop", "dev:verify": "npm --prefix .. run dev:verify", + "dev:verify:lab": "npm --prefix .. run dev:verify:lab", "dev:foreground": "npm --prefix .. run dev:foreground", + "dev:foreground:lab": "npm --prefix .. run dev:foreground:lab", "dev:frontend-only": "vite", "build": "node scripts/build-embed-assets.mjs", "preview": "vite preview", diff --git a/internal/monitoring/canonical_guardrails_test.go b/internal/monitoring/canonical_guardrails_test.go index dd75f7cf76..cf7b2fe6e3 100644 --- a/internal/monitoring/canonical_guardrails_test.go +++ b/internal/monitoring/canonical_guardrails_test.go @@ -899,6 +899,10 @@ func TestProxmoxGuestDockerInventoryUsesCanonicalReportIngestPath(t *testing.T) "m.CollectProxmoxGuestDockerInventory(ctx, allContainers)", "m.state.UpdateContainersForInstance(instanceName, allContainers)", }, + "monitor_polling_containers.go": { + "m.CollectProxmoxGuestDockerInventory(ctx, allContainers)", + "m.state.UpdateContainersForInstance(instanceName, allContainers)", + }, } for file, snippets := range requiredSnippets { diff --git a/internal/monitoring/monitor_polling_containers.go b/internal/monitoring/monitor_polling_containers.go index b2b2a9c0f6..76de799fc0 100644 --- a/internal/monitoring/monitor_polling_containers.go +++ b/internal/monitoring/monitor_polling_containers.go @@ -322,6 +322,7 @@ func (m *Monitor) pollContainersWithNodes(ctx context.Context, instanceName stri // Check Docker presence for containers that need it (new, restarted, started) allContainers = m.CheckContainersForDocker(ctx, allContainers) + m.CollectProxmoxGuestDockerInventory(ctx, allContainers) // Update state with all containers m.state.UpdateContainersForInstance(instanceName, allContainers) diff --git a/package.json b/package.json index 02fbe53f94..877e38cb93 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,18 @@ }, "scripts": { "dev": "./scripts/hot-dev-bg.sh start --takeover", + "dev:lab": "PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh start --takeover", "dev:status": "./scripts/hot-dev-bg.sh status", + "dev:status:lab": "PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh status", "dev:logs": "./scripts/hot-dev-bg.sh logs", "dev:stop": "./scripts/hot-dev-bg.sh stop", "dev:restart": "./scripts/hot-dev-bg.sh restart --takeover", + "dev:restart:lab": "PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh restart --takeover", "dev:backend-restart": "./scripts/hot-dev-bg.sh backend-restart", "dev:verify": "./scripts/hot-dev-bg.sh verify --takeover", + "dev:verify:lab": "PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh verify --takeover", "dev:foreground": "./scripts/hot-dev.sh", + "dev:foreground:lab": "PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev.sh", "test": "echo \"Error: no test specified\" && exit 1", "prepare": "husky", "mock:on": "sed -i 's/PULSE_MOCK_MODE=.*/PULSE_MOCK_MODE=true/' .env || echo 'PULSE_MOCK_MODE=true' >> .env", diff --git a/scripts/hot-dev-bg.sh b/scripts/hot-dev-bg.sh index 183e99959e..2c1d1b28ff 100755 --- a/scripts/hot-dev-bg.sh +++ b/scripts/hot-dev-bg.sh @@ -4,11 +4,14 @@ # # Usage: # npm run dev # Canonical managed dev runtime +# npm run dev:lab # LAN-bound lab-agent runtime # npm run dev:status # npm run dev:logs # npm run dev:restart +# npm run dev:restart:lab # npm run dev:backend-restart # npm run dev:verify +# npm run dev:verify:lab # ./scripts/hot-dev-bg.sh # Direct troubleshooting only set -euo pipefail @@ -568,6 +571,10 @@ start_hot_dev_child() { PULSE_DEV_API_PORT="${PULSE_DEV_API_PORT}" \ PULSE_DEV_API_URL="${PULSE_DEV_API_URL}" \ PULSE_DEV_WS_URL="${PULSE_DEV_WS_URL}" \ + PULSE_DEV_LAN="${PULSE_DEV_LAN:-}" \ + PULSE_DEV_LAB_AGENTS="${PULSE_DEV_LAB_AGENTS:-}" \ + BIND_ADDRESS="${BIND_ADDRESS:-}" \ + ALLOWED_ORIGINS="${ALLOWED_ORIGINS:-}" \ LOG_LEVEL="${LOG_LEVEL:-}" \ PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION:-}" \ PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY:-}" \ @@ -652,6 +659,10 @@ spawn_detached_supervisor() { PULSE_DEV_API_PORT="${PULSE_DEV_API_PORT}" \ PULSE_DEV_API_URL="${PULSE_DEV_API_URL}" \ PULSE_DEV_WS_URL="${PULSE_DEV_WS_URL}" \ + PULSE_DEV_LAN="${PULSE_DEV_LAN:-}" \ + PULSE_DEV_LAB_AGENTS="${PULSE_DEV_LAB_AGENTS:-}" \ + BIND_ADDRESS="${BIND_ADDRESS:-}" \ + ALLOWED_ORIGINS="${ALLOWED_ORIGINS:-}" \ LOG_LEVEL="${LOG_LEVEL:-}" \ PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION:-}" \ PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY:-}" \ @@ -1087,13 +1098,18 @@ Usage: $(basename "$0") Managed entrypoints: npm run dev + npm run dev:lab npm run dev:status + npm run dev:status:lab npm run dev:logs npm run dev:stop npm run dev:restart + npm run dev:restart:lab npm run dev:backend-restart npm run dev:verify + npm run dev:verify:lab npm run dev:foreground + npm run dev:foreground:lab Direct troubleshooting subcommands: start [--takeover] diff --git a/scripts/hot-dev.sh b/scripts/hot-dev.sh index eff6e56b50..2170860711 100755 --- a/scripts/hot-dev.sh +++ b/scripts/hot-dev.sh @@ -18,6 +18,7 @@ # PULSE_DEV_API_PORT=7655 Backend API port (default: 7655) # FRONTEND_DEV_PORT=5173 Frontend dev server port (default: 5173) # PULSE_DEV_LAN=true Expose frontend/backend on the LAN for agent/mobile testing (default: false) +# PULSE_DEV_LAB_AGENTS=true Enable LAN binding and Proxmox LXC Docker inventory for installed lab agents # LOG_LEVEL=debug Opt into verbose backend logs (default: info) # PULSE_DEV_DISABLE_BACKGROUND_AI=false # Allow automatic Patrol/discovery/alert AI in dev @@ -38,7 +39,9 @@ # # Usage: # npm run dev # Canonical managed dev runtime +# npm run dev:lab # LAN-bound lab-agent runtime # ./scripts/hot-dev.sh # Foreground/manual runtime troubleshooting +# npm run dev:foreground:lab # Foreground lab-agent troubleshooting # HOT_DEV_USE_PROD_DATA=true ./scripts/hot-dev.sh # Foreground/manual runtime with production data # set -euo pipefail @@ -357,6 +360,7 @@ PULSE_DEV_DISABLE_BACKGROUND_AI="${PULSE_DEV_DISABLE_BACKGROUND_AI:-true}" PULSE_AUTH_USER="$(hot_dev_resolve_auth_user)" PULSE_AUTH_PASS="$(hot_dev_resolve_auth_pass)" export FRONTEND_PORT PULSE_DEV_API_PORT PORT PULSE_DEV ALLOW_ADMIN_BYPASS PULSE_AUTH_USER PULSE_AUTH_PASS LOG_LEVEL PULSE_DEV_DISABLE_BACKGROUND_AI +export PULSE_DEV_LAN PULSE_DEV_LAB_AGENTS BIND_ADDRESS ALLOWED_ORIGINS export PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS # Data Directory Setup @@ -495,6 +499,10 @@ start_backend_process() { PULSE_ENCRYPTION_KEY="${PULSE_ENCRYPTION_KEY:-}" \ ALLOW_ADMIN_BYPASS="${ALLOW_ADMIN_BYPASS:-1}" \ PULSE_DEV="${PULSE_DEV:-true}" \ + PULSE_DEV_LAN="${PULSE_DEV_LAN:-}" \ + PULSE_DEV_LAB_AGENTS="${PULSE_DEV_LAB_AGENTS:-}" \ + BIND_ADDRESS="${BIND_ADDRESS:-}" \ + ALLOWED_ORIGINS="${ALLOWED_ORIGINS:-}" \ PULSE_AUTH_USER="${PULSE_AUTH_USER:-}" \ PULSE_AUTH_PASS="${PULSE_AUTH_PASS:-}" \ PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION:-}" \ diff --git a/scripts/lib/hot-dev-runtime.sh b/scripts/lib/hot-dev-runtime.sh index 8c0ee6e686..faa42da3c3 100644 --- a/scripts/lib/hot-dev-runtime.sh +++ b/scripts/lib/hot-dev-runtime.sh @@ -60,10 +60,29 @@ hot_dev_lan_enabled() { hot_dev_truthy "${PULSE_DEV_LAN:-false}" } +hot_dev_lab_agents_enabled() { + hot_dev_truthy "${PULSE_DEV_LAB_AGENTS:-false}" +} + +hot_dev_apply_lab_agent_defaults() { + if ! hot_dev_lab_agents_enabled; then + return 0 + fi + + PULSE_DEV_LAN=true + PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION=true + PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY=true + + export PULSE_DEV_LAB_AGENTS PULSE_DEV_LAN + export PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY +} + hot_dev_configure_network_defaults() { FRONTEND_PORT="${FRONTEND_PORT:-${PORT:-5173}}" PORT="${PORT:-${FRONTEND_PORT}}" + hot_dev_apply_lab_agent_defaults + if [[ -z "${LAN_IP:-}" ]]; then LAN_IP="$(hot_dev_detect_lan_ip)" fi @@ -118,6 +137,7 @@ hot_dev_configure_network_defaults() { export FRONTEND_PORT PORT export LAN_IP ALL_IPS + export PULSE_DEV_LAN PULSE_DEV_LAB_AGENTS export FRONTEND_DEV_HOST FRONTEND_DEV_PORT export PULSE_DEV_API_HOST PULSE_DEV_API_PORT PULSE_DEV_API_URL PULSE_DEV_WS_URL export BIND_ADDRESS ALLOWED_ORIGINS diff --git a/scripts/tests/test-hot-dev-bg.sh b/scripts/tests/test-hot-dev-bg.sh index 0ca53c4ccb..5b2a3676ed 100755 --- a/scripts/tests/test-hot-dev-bg.sh +++ b/scripts/tests/test-hot-dev-bg.sh @@ -551,26 +551,36 @@ with open(os.environ["PACKAGE_JSON_PATH"], "r", encoding="utf-8") as fh: for key in [ "dev", + "dev:lab", "dev:status", + "dev:status:lab", "dev:logs", "dev:stop", "dev:restart", + "dev:restart:lab", "dev:backend-restart", "dev:verify", + "dev:verify:lab", "dev:foreground", + "dev:foreground:lab", ]: print(f"{key}={scripts.get(key, '')}") PY )" assert_contains "root package exposes managed dev start" "${output}" "dev=./scripts/hot-dev-bg.sh start --takeover" + assert_contains "root package exposes lab-agent dev start" "${output}" "dev:lab=PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh start --takeover" assert_contains "root package exposes managed dev status" "${output}" "dev:status=./scripts/hot-dev-bg.sh status" + assert_contains "root package exposes lab-agent dev status" "${output}" "dev:status:lab=PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh status" assert_contains "root package exposes managed dev logs" "${output}" "dev:logs=./scripts/hot-dev-bg.sh logs" assert_contains "root package exposes managed dev stop" "${output}" "dev:stop=./scripts/hot-dev-bg.sh stop" assert_contains "root package exposes managed dev restart" "${output}" "dev:restart=./scripts/hot-dev-bg.sh restart --takeover" + assert_contains "root package exposes lab-agent dev restart" "${output}" "dev:restart:lab=PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh restart --takeover" assert_contains "root package exposes managed backend restart" "${output}" "dev:backend-restart=./scripts/hot-dev-bg.sh backend-restart" assert_contains "root package exposes managed dev verify" "${output}" "dev:verify=./scripts/hot-dev-bg.sh verify --takeover" + assert_contains "root package exposes lab-agent dev verify" "${output}" "dev:verify:lab=PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev-bg.sh verify --takeover" assert_contains "root package keeps foreground escape hatch" "${output}" "dev:foreground=./scripts/hot-dev.sh" + assert_contains "root package exposes lab-agent foreground escape hatch" "${output}" "dev:foreground:lab=PULSE_DEV_LAB_AGENTS=true ./scripts/hot-dev.sh" } test_frontend_package_exposes_managed_runtime_entrypoints() { @@ -586,13 +596,18 @@ with open(os.environ["PACKAGE_JSON_PATH"], "r", encoding="utf-8") as fh: for key in [ "dev", + "dev:lab", "dev:logs", "dev:restart", + "dev:restart:lab", "dev:backend-restart", "dev:status", + "dev:status:lab", "dev:stop", "dev:verify", + "dev:verify:lab", "dev:foreground", + "dev:foreground:lab", "dev:frontend-only", ]: print(f"{key}={scripts.get(key, '')}") @@ -600,13 +615,18 @@ PY )" assert_contains "frontend package delegates managed dev start to repo-root wrapper" "${output}" "dev=npm --prefix .. run dev" + assert_contains "frontend package delegates lab-agent dev start to repo-root wrapper" "${output}" "dev:lab=npm --prefix .. run dev:lab" assert_contains "frontend package delegates managed dev logs to repo-root wrapper" "${output}" "dev:logs=npm --prefix .. run dev:logs" assert_contains "frontend package delegates managed dev restart to repo-root wrapper" "${output}" "dev:restart=npm --prefix .. run dev:restart" + assert_contains "frontend package delegates lab-agent dev restart to repo-root wrapper" "${output}" "dev:restart:lab=npm --prefix .. run dev:restart:lab" assert_contains "frontend package delegates managed backend restart to repo-root wrapper" "${output}" "dev:backend-restart=npm --prefix .. run dev:backend-restart" assert_contains "frontend package delegates managed dev status to repo-root wrapper" "${output}" "dev:status=npm --prefix .. run dev:status" + assert_contains "frontend package delegates lab-agent dev status to repo-root wrapper" "${output}" "dev:status:lab=npm --prefix .. run dev:status:lab" assert_contains "frontend package delegates managed dev stop to repo-root wrapper" "${output}" "dev:stop=npm --prefix .. run dev:stop" assert_contains "frontend package delegates managed dev verify to repo-root wrapper" "${output}" "dev:verify=npm --prefix .. run dev:verify" + assert_contains "frontend package delegates lab-agent dev verify to repo-root wrapper" "${output}" "dev:verify:lab=npm --prefix .. run dev:verify:lab" assert_contains "frontend package delegates foreground escape hatch to repo-root wrapper" "${output}" "dev:foreground=npm --prefix .. run dev:foreground" + assert_contains "frontend package delegates lab-agent foreground escape hatch to repo-root wrapper" "${output}" "dev:foreground:lab=npm --prefix .. run dev:foreground:lab" assert_contains "frontend package keeps explicit frontend-only escape hatch" "${output}" "dev:frontend-only=vite" } @@ -695,13 +715,18 @@ test_makefile_routes_managed_runtime_through_npm() { output="$(cat "${ROOT_DIR}/Makefile")" assert_contains "make dev routes through npm wrapper" "${output}" $'dev:\n\tnpm run dev' + assert_contains "make dev-lab routes through npm wrapper" "${output}" $'dev-lab:\n\tnpm run dev:lab' assert_contains "make dev-status routes through npm wrapper" "${output}" $'dev-status:\n\tnpm run dev:status' + assert_contains "make dev-status-lab routes through npm wrapper" "${output}" $'dev-status-lab:\n\tnpm run dev:status:lab' assert_contains "make dev-logs routes through npm wrapper" "${output}" $'dev-logs:\n\tnpm run dev:logs' assert_contains "make dev-stop routes through npm wrapper" "${output}" $'dev-stop:\n\tnpm run dev:stop' assert_contains "make dev-restart routes through npm wrapper" "${output}" $'dev-restart:\n\tnpm run dev:restart' + assert_contains "make dev-restart-lab routes through npm wrapper" "${output}" $'dev-restart-lab:\n\tnpm run dev:restart:lab' assert_contains "make dev-backend-restart routes through npm wrapper" "${output}" $'dev-backend-restart:\n\tnpm run dev:backend-restart' assert_contains "make dev-verify routes through npm wrapper" "${output}" $'dev-verify:\n\tnpm run dev:verify' + assert_contains "make dev-verify-lab routes through npm wrapper" "${output}" $'dev-verify-lab:\n\tnpm run dev:verify:lab' assert_contains "make dev-foreground routes through npm wrapper" "${output}" $'dev-foreground:\n\tnpm run dev:foreground' + assert_contains "make dev-foreground-lab routes through npm wrapper" "${output}" $'dev-foreground-lab:\n\tnpm run dev:foreground:lab' assert_contains "make dev-hot routes through foreground wrapper" "${output}" $'dev-hot:\n\tnpm run dev:foreground' } @@ -717,7 +742,9 @@ test_hot_dev_script_advertises_foreground_escape_hatch() { assert_contains "hot-dev header identifies foreground escape hatch" "${output}" "hot-dev.sh - Foreground Pulse dev runtime escape hatch" assert_contains "hot-dev usage points to managed runtime first" "${output}" "npm run dev # Canonical managed dev runtime" + assert_contains "hot-dev usage advertises lab-agent managed runtime" "${output}" "npm run dev:lab # LAN-bound lab-agent runtime" assert_contains "hot-dev usage reserves direct script for manual troubleshooting" "${output}" "./scripts/hot-dev.sh # Foreground/manual runtime troubleshooting" + assert_contains "hot-dev usage advertises lab-agent foreground troubleshooting" "${output}" "npm run dev:foreground:lab # Foreground lab-agent troubleshooting" assert_not_contains "hot-dev usage no longer claims standard dev mode" "${output}" "Standard dev mode" } @@ -871,6 +898,9 @@ test_hot_dev_bg_script_advertises_managed_entrypoint() { output="$(cat "${ROOT_DIR}/scripts/hot-dev-bg.sh")" assert_contains "hot-dev-bg usage points to managed runtime first" "${output}" "npm run dev # Canonical managed dev runtime" + assert_contains "hot-dev-bg usage advertises lab-agent managed runtime" "${output}" "npm run dev:lab # LAN-bound lab-agent runtime" + assert_contains "hot-dev-bg usage advertises lab-agent restart" "${output}" "npm run dev:restart:lab" + assert_contains "hot-dev-bg usage advertises lab-agent verify" "${output}" "npm run dev:verify:lab" assert_contains "hot-dev-bg documents direct launcher as troubleshooting only" "${output}" "./scripts/hot-dev-bg.sh # Direct troubleshooting only" assert_contains "hot-dev-bg sources shared runtime helpers" "${output}" 'source "${SCRIPT_DIR}/lib/hot-dev-runtime.sh"' assert_contains "hot-dev-bg uses shared network defaults" "${output}" "hot_dev_configure_network_defaults" @@ -889,6 +919,9 @@ test_hot_dev_bg_preserves_proxmox_guest_docker_env() { assert_contains "hot-dev-bg child preserves Proxmox guest Docker detection opt-in" "${output}" 'PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION:-}"' assert_contains "hot-dev-bg child preserves Proxmox guest Docker inventory opt-in" "${output}" 'PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY:-}"' assert_contains "hot-dev-bg child preserves scoped Proxmox guest Docker VMIDs" "${output}" 'PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS="${PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS:-}"' + assert_contains "hot-dev-bg child preserves lab-agent mode" "${output}" 'PULSE_DEV_LAB_AGENTS="${PULSE_DEV_LAB_AGENTS:-}"' + assert_contains "hot-dev-bg child preserves LAN opt-in" "${output}" 'PULSE_DEV_LAN="${PULSE_DEV_LAN:-}"' + assert_contains "hot-dev-bg child preserves backend bind address" "${output}" 'BIND_ADDRESS="${BIND_ADDRESS:-}"' assert_contains "hot-dev-bg child preserves debug log level" "${output}" 'LOG_LEVEL="${LOG_LEVEL:-}"' assert_contains "hot-dev-bg restart takeover clears launchd before env-sensitive relaunch" "${output}" 'stop_launchd_hot_dev_job' } @@ -1032,7 +1065,10 @@ test_hot_dev_bg_usage_prefers_managed_wrappers() { assert_contains "hot-dev-bg usage shows managed entrypoints heading" "${output}" "Managed entrypoints:" assert_contains "hot-dev-bg usage advertises npm dev wrapper" "${output}" "npm run dev" + assert_contains "hot-dev-bg usage advertises lab-agent dev wrapper" "${output}" "npm run dev:lab" + assert_contains "hot-dev-bg usage advertises lab-agent status wrapper" "${output}" "npm run dev:status:lab" assert_contains "hot-dev-bg usage advertises npm verify wrapper" "${output}" "npm run dev:verify" + assert_contains "hot-dev-bg usage advertises lab-agent verify wrapper" "${output}" "npm run dev:verify:lab" assert_contains "hot-dev-bg usage labels raw commands as troubleshooting-only" "${output}" "Direct troubleshooting subcommands:" assert_contains "hot-dev-bg usage retains direct start subcommand" "${output}" "start [--takeover]" } diff --git a/scripts/tests/test-hot-dev-runtime.sh b/scripts/tests/test-hot-dev-runtime.sh index 989fc7497a..ed12fd199d 100755 --- a/scripts/tests/test-hot-dev-runtime.sh +++ b/scripts/tests/test-hot-dev-runtime.sh @@ -133,7 +133,11 @@ test_hot_dev_preserves_proxmox_guest_docker_env() { local output output="$(sed -n '1,760p' "${HOT_DEV}")" + assert_contains "hot-dev exports lab-agent mode opt-in" "${output}" "export PULSE_DEV_LAN PULSE_DEV_LAB_AGENTS BIND_ADDRESS ALLOWED_ORIGINS" assert_contains "hot-dev exports Proxmox guest Docker detection opt-in" "${output}" "export PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS" + assert_contains "backend launch preserves LAN opt-in" "${output}" 'PULSE_DEV_LAN="${PULSE_DEV_LAN:-}"' + assert_contains "backend launch preserves lab-agent mode opt-in" "${output}" 'PULSE_DEV_LAB_AGENTS="${PULSE_DEV_LAB_AGENTS:-}"' + assert_contains "backend launch preserves backend bind address" "${output}" 'BIND_ADDRESS="${BIND_ADDRESS:-}"' assert_contains "backend launch preserves Proxmox guest Docker detection opt-in" "${output}" 'PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION:-}"' assert_contains "backend launch preserves Proxmox guest Docker inventory opt-in" "${output}" 'PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY="${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY:-}"' assert_contains "backend launch preserves scoped Proxmox guest Docker VMIDs" "${output}" 'PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS="${PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS:-}"' @@ -201,6 +205,45 @@ test_hot_dev_network_defaults_are_local_first_with_explicit_lan_opt_in() { assert_contains "LAN opt-in allows the wildcard dev origin Electron may report" "${output}" "http://0.0.0.0:5173" } +test_hot_dev_lab_agent_mode_enables_lan_and_guest_docker_inventory_defaults() { + local output + output="$( + HOT_DEV_RUNTIME_LIB="${HOT_DEV_RUNTIME_LIB}" \ + bash -c ' + set -euo pipefail + source "${HOT_DEV_RUNTIME_LIB}" + unset FRONTEND_PORT PORT FRONTEND_DEV_HOST FRONTEND_DEV_PORT BIND_ADDRESS + unset PULSE_DEV_LAN PULSE_DEV_API_HOST PULSE_DEV_API_PORT PULSE_DEV_API_URL PULSE_DEV_WS_URL + unset PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY + unset ALLOWED_ORIGINS + LAN_IP=192.168.50.10 + ALL_IPS="192.168.50.10 10.10.10.5" + PULSE_DEV_LAB_AGENTS=true + hot_dev_configure_network_defaults + printf "lab_mode=%s\n" "${PULSE_DEV_LAB_AGENTS}" + printf "lab_lan=%s\n" "${PULSE_DEV_LAN}" + printf "lab_detection=%s\n" "${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION}" + printf "lab_inventory=%s\n" "${PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY}" + printf "lab_frontend_host=%s\n" "${FRONTEND_DEV_HOST}" + printf "lab_api_host=%s\n" "${PULSE_DEV_API_HOST}" + printf "lab_api_url=%s\n" "${PULSE_DEV_API_URL}" + printf "lab_bind=%s\n" "${BIND_ADDRESS}" + printf "lab_origins=%s\n" "${ALLOWED_ORIGINS}" + ' + )" + + assert_contains "lab-agent mode remains explicitly marked" "${output}" "lab_mode=true" + assert_contains "lab-agent mode enables LAN runtime exposure" "${output}" "lab_lan=true" + assert_contains "lab-agent mode enables Proxmox guest Docker detection" "${output}" "lab_detection=true" + assert_contains "lab-agent mode enables Proxmox guest Docker inventory" "${output}" "lab_inventory=true" + assert_contains "lab-agent mode exposes the frontend on all interfaces" "${output}" "lab_frontend_host=0.0.0.0" + assert_contains "lab-agent mode targets the detected LAN API host" "${output}" "lab_api_host=192.168.50.10" + assert_contains "lab-agent mode derives the LAN API URL" "${output}" "lab_api_url=http://192.168.50.10:7655" + assert_contains "lab-agent mode binds the backend on all interfaces" "${output}" "lab_bind=0.0.0.0" + assert_contains "lab-agent mode allows detected LAN frontend origins" "${output}" "lab_origins=http://192.168.50.10" + assert_contains "lab-agent mode allows the wildcard dev origin Electron may report" "${output}" "http://0.0.0.0:5173" +} + test_hot_dev_browser_urls_distinguish_bind_and_browser_hosts() { local output output="$( @@ -228,6 +271,7 @@ test_hot_dev_keeps_backend_launch_errors_in_debug_log test_hot_dev_preserves_proxmox_guest_docker_env test_hot_dev_avoids_self_killing_npm_wrapper test_hot_dev_network_defaults_are_local_first_with_explicit_lan_opt_in +test_hot_dev_lab_agent_mode_enables_lan_and_guest_docker_inventory_defaults test_hot_dev_browser_urls_distinguish_bind_and_browser_hosts if (( failures > 0 )); then