diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7195769..623f617 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,9 +99,9 @@ jobs: run: ./webview/hugo/test/run.sh # --------------------------------------------------------------------- - # Stability: only required on the active release branch. Mirrors + # Stability: only required on release branches. Mirrors # scripts/stability-check.sh but capped to a few minutes so CI stays - # affordable. The real soak is driven manually per docs/STABILITY.md. + # affordable. Longer manual runs are documented in docs/STABILITY.md. # --------------------------------------------------------------------- stability: name: stability smoke @@ -116,7 +116,7 @@ jobs: cache: true - name: Build (race) - run: CGO_ENABLED=1 go build -race -o monitor ./cmd/monitor + run: CGO_ENABLED=1 go build -race -o workstation-probe ./cmd/monitor - name: Run short soak env: diff --git a/.gitignore b/.gitignore index 58b7f6a..8a9486d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ config.yaml # Go build outputs and test artifacts /monitor /monitor.race +/workstation-probe +/workstation-probe.race *.exe *.exe~ *.dll diff --git a/CHANGELOG.md b/CHANGELOG.md index bdfad91..1001fb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ breaking changes between `0.x` releases are expected. - CORS origin parser with allow / deny / preflight middleware. - Request-id, access-log, recovery, and token-bucket rate-limit middleware. - Hugo webview module with snapshot test fixture. -- `contrib/systemd` unit for installing monitor as a service. +- `contrib/systemd` unit for installing workstation-probe as a service. ### Changed diff --git a/README.md b/README.md index e12c85a..0ac5d94 100644 --- a/README.md +++ b/README.md @@ -7,22 +7,92 @@ mount tables). ## Quick start +### Method 1: run in the foreground + ```bash # 1. Build -make build # CGO_ENABLED=1, stub GPU collector -make build-nvml # adds real NVML support (requires libnvidia-ml) +make build # portable build with the stub GPU collector +# or, on NVIDIA hosts with libnvidia-ml: +# make build-nvml # 2. Configure cp config.example.yaml config.yaml $EDITOR config.yaml # set server.port, optionally tweak mount_points # 3. Run -./monitor -config config.yaml +./workstation-probe -config config.yaml # 4. Smoke test (in another shell) ./scripts/smoke.sh http://localhost:19090 ``` +### Method 2: install as a systemd service + +Use the install scripts when you want `workstation-probe` managed by +systemd instead of running in the foreground. + +For a root/system install: + +```bash +sudo ./scripts/install.sh + +# Optional variants: +# sudo ./scripts/install.sh --no-start # install without starting it +# sudo ./scripts/install.sh --port 9090 # set server.port when creating a new config +# sudo ./scripts/install.sh --nvml # build with NVML GPU support +# sudo ./scripts/install.sh --nvml --port 9090 +``` + +This installs the binary under `/usr/local/bin/`, creates +`/etc/workstation-probe/config.yaml` if it is missing, and writes the +system service to `/etc/systemd/system/workstation-probe.service`. + +Useful service commands: + +```bash +systemctl status workstation-probe +journalctl -u workstation-probe -f + +# Stop or restart: +# sudo systemctl stop workstation-probe +# sudo systemctl restart workstation-probe + +# Uninstall: +# sudo make uninstall # removes binary + system service; keeps /etc/workstation-probe/config.yaml +``` + +For a rootless user service: + +```bash +./scripts/rootless-install.sh + +# Optional variants: +# ./scripts/rootless-install.sh --no-start # install without starting it +# ./scripts/rootless-install.sh --port 9090 # set server.port when creating a new config +# ./scripts/rootless-install.sh --nvml # build with NVML GPU support +# ./scripts/rootless-install.sh --nvml --port 9090 +``` + +This installs the binary under `~/.local/bin/`, creates +`~/.config/workstation-probe/config.yaml` if it is missing, and writes +the user service to `~/.config/systemd/user/workstation-probe.service`. + +Useful user-service commands: + +```bash +systemctl --user status workstation-probe +journalctl --user -u workstation-probe -f + +# Stop or restart: +# systemctl --user stop workstation-probe +# systemctl --user restart workstation-probe + +# Uninstall: +# systemctl --user disable --now workstation-probe +# rm -f ~/.config/systemd/user/workstation-probe.service ~/.local/bin/workstation-probe +# systemctl --user daemon-reload +``` + ## Endpoints | Method | Path | Purpose | @@ -81,37 +151,23 @@ name: rig-02 api: "http://rig-02.lan:19090" refresh: "5s" mode: peak -window: 5s +window: "5s" modules: [cpu, memory, gpu, storage] ``` The `gpu` key is absent when the module is disabled. -### GPU utilization and webview cell status +### GPU fields When NVML is available, each `gpu.devices[*]` entry carries `utilization_gpu_percent`, the GPU core utilization reported by NVML -over its most recent sampling period. The webview renders that value as -the first stat row in each GPU card. +over its most recent sampling period. Each entry may also carry `power_limit_watts` (the current power management limit, in W, from `GetPowerManagementLimit`). Consumer GeForce cards commonly return `NVML_ERROR_NOT_SUPPORTED`; in that case the field is omitted. -The webview uses GPU utilization together with `memory_used_bytes / -memory_total_bytes` and `power_draw_watts / power_limit_watts` to colour -each per-GPU cell background — see -`webview/hugo/static/workstation-probe/js/render.js::gpuStatusPercent` -and `gpuStatus`. Thresholds: - -- `< 10%` → **idle** (green tint) -- `10-60%` → **working** (amber tint) -- `≥ 60%` → **busy** (red tint) - -Where "the percentage" is -`max(gpu_util_pct, memory_occupancy_pct, power_draw_pct)`. - ### Error semantics A module's `Sample` carries an `error` string when the underlying syscall @@ -120,7 +176,9 @@ clients should treat them as "unknown" rather than "really zero". ## Configuration -`config.yaml` (YAML). Every field has a default except `server.port`. +`config.yaml` (YAML). `server.port` is required. Most other fields have +defaults; when storage is enabled, `modules.storage.mount_points` must +contain at least one real mount point. ```yaml server: @@ -156,43 +214,24 @@ logging: format: json ``` -## systemd installation +## systemd hardening -```bash -sudo make install # uses MONITOR_USER=$(id -un) by default -sudo MONITOR_USER=alice make install -sudo $EDITOR /etc/monitor/config.yaml # set server.port; set server.host only if exposing beyond localhost -sudo make start # systemctl enable --now monitor -systemctl status monitor -sudo make stop -sudo make uninstall -``` - -The `make install` target performs: - -1. Copies `./monitor` to `/usr/local/bin/monitor` (mode 0755). -2. Creates `/etc/monitor/` and copies `config.example.yaml` to - `/etc/monitor/config.yaml` if it does not already exist. -3. Renders `contrib/systemd/monitor.service.in` into - `/etc/systemd/system/monitor.service` after substituting `@USER@` with - the value of `MONITOR_USER` (default: the current user). -4. Runs `systemctl daemon-reload`. - -`make uninstall` removes the binary and the service file but keeps -`/etc/monitor/config.yaml` so a subsequent install keeps the user's config. +For the root/system install, `scripts/install.sh` writes +`/etc/systemd/system/workstation-probe.service` from +`contrib/systemd/workstation-probe.service.in`. That template enables a +full systemd sandbox (`NoNewPrivileges`, `ProtectSystem=strict`, +`ProtectHome`, `RestrictAddressFamilies`, `RestrictNamespaces`, +`PrivateTmp`, etc.). The only flag deliberately omitted is +`MemoryDenyWriteExecute` because NVML's driver-side code can produce +transient W^X mappings on some hosts. -### Hardening +The rootless installer writes a separate user service and does not include +those sandboxing directives. -The shipped `monitor.service.in` already enables a full systemd -sandbox (`NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, -`RestrictAddressFamilies`, `RestrictNamespaces`, `PrivateTmp`, etc., -see `contrib/systemd/monitor.service.in`). The only flag deliberately -omitted is `MemoryDenyWriteExecute` because NVML's driver-side code -can produce transient W^X mappings on some hosts. - -If you need to relax a directive (e.g. to allow a non-standard config -path), edit `/etc/systemd/system/monitor.service` after `make install`, -then `sudo systemctl daemon-reload && sudo systemctl restart monitor`. +If you need to relax a directive for the root/system service (e.g. to allow +a non-standard config path), edit +`/etc/systemd/system/workstation-probe.service` after installation, then +`sudo systemctl daemon-reload && sudo systemctl restart workstation-probe`. ## Deployment with TLS @@ -206,17 +245,17 @@ intentionally want the probe reachable directly from other machines. ```nginx server { listen 443 ssl http2; - server_name monitor.internal.example.com; + server_name workstation-probe.internal.example.com; - ssl_certificate /etc/nginx/ssl/monitor.crt; - ssl_certificate_key /etc/nginx/ssl/monitor.key; + ssl_certificate /etc/nginx/ssl/workstation-probe.crt; + ssl_certificate_key /etc/nginx/ssl/workstation-probe.key; # pass client IP for the rate limiter (trust_proxy_headers must be true) proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; # optional: basic auth (or replace with your auth mechanism) - auth_basic "monitor"; + auth_basic "workstation-probe"; auth_basic_user_file /etc/nginx/.htpasswd; location / { @@ -236,16 +275,6 @@ CORS, rate limiting and basic auth are **not** authentication. They are mitigations; deploy them as defense-in-depth, not as the sole access control. -## Intranet NAS mirror - -For a zero-budget lab dashboard, keep the public Hugo blog on GitHub Pages -and serve the live workstation page from an intranet NAS mirror. The public -site should use a normal link to the NAS page; it should not iframe or fetch -LAN-only HTTP APIs from GitHub Pages. - -See `docs/intranet-mirror.md` and -`webview/hugo/nas-overlay.example/` for the overlay layout and CORS rules. - ## Architecture ``` @@ -285,7 +314,7 @@ machines without `libnvidia-ml.so`. resulting binary reports live data on NVIDIA hosts. For webview development, `./scripts/dev-server.sh` defaults to -`GPU=auto`: it rebuilds `./monitor` before starting, uses the `nvml` +`GPU=auto`: it rebuilds a local development binary before starting, uses the `nvml` build tag when `libnvidia-ml.so` is available, and falls back to the stub collector otherwise. Use `GPU=nvml ./scripts/dev-server.sh` to force a real-NVML build, or `GPU=stub ./scripts/dev-server.sh` to force @@ -315,13 +344,12 @@ and prints a human-readable summary of CPU/GPU/memory/storage state. It chooses one of two modes automatically: - **systemd mode** (preferred, used when `sudo -n true` works): installs - to `/usr/local/bin/monitor` and uses a uniquely-named unit - `monitor-live-test.service` so a real install under the name `monitor` - is never touched. Cleanup unregisters the unit and deletes the files. -- **direct mode** (fallback, when sudo isn't available): installs to - `~/.local/bin/monitor-live-test` and runs the binary directly via `nohup`. The - PID is tracked in a file; cleanup `kill`s it and removes the install - directory. + an isolated test binary and uniquely named systemd unit, so a regular + `workstation-probe` install is not touched. Cleanup unregisters the unit + and deletes the files. +- **direct mode** (fallback, when sudo isn't available): installs an isolated + user-writable test binary and runs it directly via `nohup`. The PID is + tracked in a file; cleanup `kill`s it and removes the install directory. Override the port: `PORT=19191 make live-test` Force a specific mode: `MODE=direct make live-test` or `MODE=systemd make live-test` diff --git a/docs/STABILITY.md b/docs/STABILITY.md index a1234e8..efa499b 100644 --- a/docs/STABILITY.md +++ b/docs/STABILITY.md @@ -1,73 +1,42 @@ -# Stability Acceptance — 0.1.0 +# Stability check -The `release/0.1.0` branch exists to converge on a stable, releasable 0.1.0 build. -Nothing new lands here — only bug fixes, tightening of acceptance, and docs that -match what we actually ship. +`scripts/stability-check.sh` runs a short or long soak against a local +`workstation-probe` instance and records request-health artifacts under +`build/stability//`. -This document defines "done" for the stability effort. +Use it before tagging a release, after sampler or shutdown changes, and when +checking behavior on real hardware. -## Exit criteria +## Run -The branch is considered stable when **every** item below holds for at least one -72-hour soak run on real hardware, plus the targeted regression suites pass on CI. - -### Functional must-pass - -- [ ] `make test` is green (race detector, both build tags). -- [ ] `make vet` is green. -- [ ] `make lint` is green. -- [ ] `make hugo-snapshot` is green (`webview/hugo/exampleSite`). -- [ ] `make build` and `make build-nvml` both succeed. -- [ ] Smoke (`scripts/smoke.sh`) against a direct-run instance returns 200 for - every public route under both `/metrics` and `/health`. -- [ ] `scripts/stability-check.sh` exits 0 (see "Soak" below). - -### Soak (runtime) - -`scripts/stability-check.sh 72h` (configurable via `STABILITY_DURATION`) must: - -- [ ] Hold the process RSS delta under **+10 %** of its value at minute 1. -- [ ] Keep `p99 sample latency` for `/metrics` under **50 ms** for the whole run. -- [ ] Produce **zero** non-recovery goroutine count drift - (`runtime.NumGoroutine()` stable ±2 over 10-minute windows). -- [ ] Survive two intentional `kill -HUP` reload cycles without losing - in-flight requests. -- [ ] Resume cleanly after `kill -STOP` / `kill -CONT` without leaking fds. -- [ ] Record no `DATA RACE` lines from `-race` builds. - -### Operational +```bash +# Default: 5-minute direct run on port 19090. +./scripts/stability-check.sh -- [ ] `systemd` unit (`contrib/systemd/monitor.service`) starts and stops cleanly - under `make live-test`. -- [ ] Logs are structured JSON (slog) and rotate without losing sample data. -- [ ] `config.yaml` schema validates; bad config refuses to start with a - useful error. +# Longer run. +STABILITY_DURATION=72h ./scripts/stability-check.sh -### Documentation +# Use an existing binary path and port. +MONITOR_BIN=./workstation-probe MONITOR_PORT=19191 ./scripts/stability-check.sh -- [ ] `README.md` documents `--version`, log format, and the config schema. -- [ ] `CHANGELOG.md` has the **0.1.0** entry listing the closed issues. +# Rebuild with the race detector before running. +RACE_BUILD=1 ./scripts/stability-check.sh +``` -## How to drive the soak +## Modes -```bash -# Quick local check (5 minutes). Use during development. -make stability-check STABILITY_DURATION=5m - -# Full 72-hour run on dedicated hardware or a VM. -make stability-check STABILITY_DURATION=72h -``` +- `MODE=direct` starts the binary directly with a generated config. +- `MODE=systemd` restarts and stops the systemd service expected by the script. -The script writes to `build/stability//`: +## Artifacts -- `metrics.log` — sampler output as JSON lines, one record per minute. -- `goroutine.log` — `runtime.NumGoroutine()` over time. -- `summary.json` — pass / fail per acceptance item. +The script writes: -## Merging strategy +- `config.yaml` — generated direct-mode config. +- `workstation-probe.log` — server stdout/stderr for direct mode. +- `workstation-probe.pid` — direct-mode process pid. +- `sample.log` — periodic `/metrics` request status and latency. +- `goroutine.log` — periodic `/health` snapshots. +- `summary.json` — run duration, mode, port, sample count, and error count. -- All fixes land as PRs into `release/0.1.0` with the - `stability` label and `[no-ff]` merge strategy. -- After merging into `main`, the `v0.1.0` tag is cut from `main`'s tip. -- The `release/0.1.0` branch is then archived (`git branch -a` keeps the ref) - but no new PRs are accepted against it. +The check fails if any periodic `/metrics` request records an error. diff --git a/docs/integrate-with-hugo.md b/docs/integrate-with-hugo.md index 3a03c4c..8f77dbd 100644 --- a/docs/integrate-with-hugo.md +++ b/docs/integrate-with-hugo.md @@ -6,10 +6,10 @@ copying, no manual file management. Hugo handles versioning and updates. ## Prerequisites -- **Hugo** extended ≥ 0.163.3 (`hugo version`) +- **Hugo** ≥ 0.158.0 (`hugo version`) - **Go** ≥ 1.21 (required by Hugo's module system; `go version`) - An existing Hugo blog with a `go.mod` file (run `hugo mod init` if missing) -- A running `workstation-probe` monitor instance (see the project README) +- A running `workstation-probe` server instance (see the project README) ## Step 1: Add the Hugo Module @@ -69,8 +69,7 @@ partial before the closing `` tag: ``` -If you don't have a `baseof.html` yet, copy the minimal example from the -module: +If you don't have a `baseof.html` yet, start from your theme's base template: ```bash cp themes/your-theme/layouts/_default/baseof.html layouts/_default/baseof.html @@ -96,7 +95,7 @@ modules: ``` - `name` — displayed in the panel header -- `api` — the monitor server base URL (no trailing slash) +- `api` — the `workstation-probe` server base URL (no trailing slash) - `refresh` — poll interval as a Go duration string (`5s`, `10s`, `30s`, `1m`) - `modules` — which metric panels to show (one or more of: `cpu`, `memory`, `gpu`, `storage`) @@ -144,12 +143,12 @@ hugo server ``` Visit `http://localhost:1313` and navigate to the page with the shortcode. -You should see a live metrics panel with charts pulling data from the monitor. +You should see a live metrics panel rendering data from the server. ## CORS configuration -If your Hugo dev server and the monitor run on different ports (the default -setup), you need CORS enabled on the monitor. Edit the monitor's `config.yaml`: +If your Hugo dev server and the `workstation-probe` server run on different +ports, enable CORS in the server's `config.yaml`: ```yaml security: @@ -159,8 +158,8 @@ security: - http://localhost:1313 ``` -For production, if Hugo and the monitor are served behind the same reverse -proxy (nginx, Caddy), CORS is unnecessary. +For production, if Hugo and the `workstation-probe` server are served behind +the same reverse proxy (nginx, Caddy), CORS is unnecessary. ## Troubleshooting @@ -198,8 +197,8 @@ refresh: "5s" ### "CORS error" or "Failed to fetch" in browser console -The browser is blocking cross-origin requests. Enable CORS on the monitor -(see the CORS section above), or serve both the site and the monitor from +The browser is blocking cross-origin requests. Enable CORS on the server +(see the CORS section above), or serve both the site and the server from the same origin. ### "Chart.js is not defined" or blank panel @@ -212,7 +211,7 @@ intranet without internet access, self-host Chart.js. Download ### Tests fail with "Hugo version mismatch" -The module requires Hugo extended ≥ 0.163.3. Check your Hugo version: +The module requires Hugo ≥ 0.158.0. Check your Hugo version: ```bash hugo version diff --git a/docs/intranet-mirror.md b/docs/intranet-mirror.md deleted file mode 100644 index afab37a..0000000 --- a/docs/intranet-mirror.md +++ /dev/null @@ -1,127 +0,0 @@ -# Intranet NAS mirror - -This project supports a zero-budget deployment model where the public blog -stays on GitHub Pages, while the live workstation dashboard is served by an -intranet NAS mirror. - -## Goal - -The public site should only link to the intranet page. It must not iframe the -dashboard and must not fetch workstation APIs from GitHub Pages: - -```text -public GitHub Pages blog - -> normal link to http://nas-host-or-ip/lab-blog/posts/workstation-monitor/ - -intranet NAS mirror - -> same Hugo theme, same navigation, same article layout - -> fetches http://workstation-lan-ip:19090 from the browser -``` - -This avoids browser mixed-content / private-network restrictions because the -HTTPS public page does not fetch private HTTP resources. It also keeps live -metrics inside the LAN. - -## Repository roles - -Use one canonical GitHub repository and one private NAS overlay: - -```text -GitHub repository - public content, theme, workstation-probe Hugo module, no real LAN config - -NAS overlay - private intranet page, workstation IPs/hostnames, NAS baseURL override - -NAS build workspace - clean repo clone + overlay copied on top, then hugo build -``` - -Do not edit the NAS clone directly. Build from a clean clone plus overlay every -time so `git pull` cannot be blocked by local changes. - -## Overlay example - -`webview/hugo/nas-overlay.example/` is a committed template for the private -NAS overlay. Copy it to the NAS and edit the placeholders there. The real -overlay should normally live outside the Git repository, for example: - -```text -/volume1/lab-blog/overlay/ - config/intranet/hugo.yaml - content/posts/workstation-monitor/index.md - content/posts/workstation-monitor/ws01.yaml - content/posts/workstation-monitor/ws02.yaml -``` - -The example uses page-local YAML files beside the monitor article, so each -panel config is resolved through the shortcode's page-relative lookup. - -## Build composition - -A NAS builder can compose the site with ordinary file copies: - -```bash -rsync -a --delete repo/ work/ -rsync -a overlay/ work/ -hugo --source work --environment intranet --destination publish --cleanDestinationDir -rsync -a --delete publish/ /volume1/web/lab-blog/ -``` - -`--environment intranet` makes Hugo read `config/intranet/hugo.yaml` from the -overlay. Use that file to override `baseURL` for the NAS mirror. - -## Public link page - -The GitHub Pages version should use a normal link: - -```markdown -[Open the intranet workstation monitor](http://nas-host-or-ip/lab-blog/posts/workstation-monitor/) -``` - -Do not use an iframe, and do not embed `workstation-probe-panel` on the public -GitHub Pages page unless the APIs are also served over public HTTPS. - -## Workstation CORS - -When the NAS page fetches a workstation API on a different host/port, each -workstation must allow the NAS origin. The origin is only scheme + host + port; -it does not include the path: - -```yaml -server: - host: 0.0.0.0 # or a specific LAN address - port: 19090 - -security: - cors: - enabled: true - allowed_origins: - - http://nas-host-or-ip - - http://nas-host-or-ip:1313 - rate_limit: - enabled: true - requests_per_second: 10 - burst: 20 - trust_proxy_headers: false - exempt_paths: [/health] -``` - -Bind only to the LAN interface when possible, and use host firewall rules if -the workstation has any interface outside the trusted lab network. - -## Asset note - -The Hugo module imports Chart.js from a CDN by default. If the NAS mirror must -work without internet access, vendor Chart.js into the consuming site and -override `static/workstation-probe/js/charts.js` to import the local vendored -copy. See `webview/hugo/README.md` for the vendoring note. - -## Security boundary - -- Live metrics stay on the LAN; the public site contains only a link. -- Internet visitors can see the link but cannot reach RFC1918/LAN addresses. -- CORS is not authentication. It only controls browser reads from allowed - origins. -- Do not commit the real NAS overlay when it contains hostnames, IPs, tokens, - or lab-specific topology. diff --git a/scripts/dev-server.sh b/scripts/dev-server.sh index 1687378..96b8042 100755 --- a/scripts/dev-server.sh +++ b/scripts/dev-server.sh @@ -13,7 +13,8 @@ # REFRESH - sampler interval as Go duration (default: 1s) # GPU - GPU build mode: auto, nvml, or stub (default: auto) # auto uses NVML when libnvidia-ml is available, otherwise stub -# REBUILD - rebuild ./monitor before starting: 1 or 0 (default: 1) +# REBUILD - rebuild the local development binary before starting: +# 1 or 0 (default: 1) set -euo pipefail @@ -27,7 +28,7 @@ REFRESH="${REFRESH:-1s}" GPU="${GPU:-auto}" REBUILD="${REBUILD:-1}" CONFIG="/tmp/workstation-probe-dev-config.yaml" -BIN="$HERE/monitor" +BIN="$HERE/workstation-probe" cleanup() { rm -f "$CONFIG" @@ -52,7 +53,7 @@ build_monitor() { case "$GPU" in auto) if nvml_library_available; then - echo ">> building monitor with NVML support (GPU=auto)" + echo ">> building workstation-probe with NVML support (GPU=auto)" if make build-nvml; then echo " gpu build : nvml" return 0 @@ -65,12 +66,12 @@ build_monitor() { echo " gpu build : stub" ;; nvml|1|true|yes) - echo ">> building monitor with NVML support (GPU=${GPU})" + echo ">> building workstation-probe with NVML support (GPU=${GPU})" make build-nvml echo " gpu build : nvml" ;; stub|0|false|no) - echo ">> building monitor with stub GPU collector (GPU=${GPU})" + echo ">> building workstation-probe with stub GPU collector (GPU=${GPU})" make build echo " gpu build : stub" ;; @@ -139,7 +140,7 @@ EOF if [ "$REBUILD" = "0" ]; then if [ ! -x "$BIN" ]; then - echo ">> ERROR: monitor binary not found and REBUILD=0" >&2 + echo ">> ERROR: development binary not found and REBUILD=0" >&2 echo " run without REBUILD=0, or build manually with 'make build-nvml'" >&2 exit 1 fi diff --git a/scripts/install.sh b/scripts/install.sh index a29e6fb..54284aa 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -52,7 +52,7 @@ usage() { cat </dev/null sudo rm -f "${SERVICE_FILE}" sudo systemctl daemon-reload 2>/dev/null - sudo rm -rf "/etc/monitor-live-test" + sudo rm -rf "/etc/workstation-probe-live-test" sudo rm -f "${BIN_DEST}" sudo pkill -f "${BIN_DEST}" 2>/dev/null ;; @@ -163,8 +163,8 @@ logging: EOF echo "$LOG_PREFIX Rendering ${SERVICE_FILE}" ${SUDO} sed -e "s/@USER@/${USER:-$(id -un)}/g" \ - -e "s|/usr/local/bin/monitor|${BIN_DEST}|g" \ - -e "s|/etc/monitor/config.yaml|${CONFIG_FILE}|g" \ + -e "s|/usr/local/bin/workstation-probe|${BIN_DEST}|g" \ + -e "s|/etc/workstation-probe/config.yaml|${CONFIG_FILE}|g" \ "${SERVICE_TEMPLATE}" | ${SUDO} tee "${SERVICE_FILE}" >/dev/null ${SUDO} systemctl daemon-reload ${SUDO} systemctl enable --now "${SERVICE}.service" diff --git a/scripts/rootless-install.sh b/scripts/rootless-install.sh index dd642e6..f6fb3d3 100755 --- a/scripts/rootless-install.sh +++ b/scripts/rootless-install.sh @@ -52,7 +52,7 @@ usage() { cat < "${SERVICE_FILE}" </) @@ -16,7 +16,7 @@ set -euo pipefail DURATION="${STABILITY_DURATION:-5m}" MODE="${MODE:-direct}" -BIN="${MONITOR_BIN:-./monitor}" +BIN="${MONITOR_BIN:-./workstation-probe}" PORT="${MONITOR_PORT:-19090}" RACE_BUILD="${RACE_BUILD:-0}" @@ -41,12 +41,12 @@ echo build() { if [ "$RACE_BUILD" = "1" ]; then echo ">> rebuilding with -race" - CGO_ENABLED=1 go build -race -o monitor.race ./cmd/monitor - BIN=./monitor.race + CGO_ENABLED=1 go build -race -o workstation-probe.race ./cmd/monitor + BIN=./workstation-probe.race else if [ ! -x "$BIN" ]; then - echo ">> building monitor" - CGO_ENABLED=1 go build -o monitor ./cmd/monitor + echo ">> building workstation-probe" + CGO_ENABLED=1 go build -o workstation-probe ./cmd/monitor fi fi } @@ -59,11 +59,11 @@ server: host: 127.0.0.1 port: $PORT YAML - "$BIN" -config "$ARTIFACT_DIR/config.yaml" >"$ARTIFACT_DIR/monitor.log" 2>&1 & - echo $! >"$ARTIFACT_DIR/monitor.pid" + "$BIN" -config "$ARTIFACT_DIR/config.yaml" >"$ARTIFACT_DIR/workstation-probe.log" 2>&1 & + echo $! >"$ARTIFACT_DIR/workstation-probe.pid" ;; systemd) - sudo systemctl restart monitor + sudo systemctl restart workstation-probe ;; *) echo "unknown MODE=$MODE" >&2 @@ -75,14 +75,14 @@ YAML stop_monitor() { case "$MODE" in direct) - if [ -f "$ARTIFACT_DIR/monitor.pid" ]; then - pid=$(cat "$ARTIFACT_DIR/monitor.pid") + if [ -f "$ARTIFACT_DIR/workstation-probe.pid" ]; then + pid=$(cat "$ARTIFACT_DIR/workstation-probe.pid") kill "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true fi ;; systemd) - sudo systemctl stop monitor + sudo systemctl stop workstation-probe ;; esac } @@ -131,7 +131,7 @@ if [[ "$DURATION" == *m ]]; then mins="${DURATION%m}" if [ -n "$mins" ] && [ "$mins" -ge 2 ] 2>/dev/null; then half=$((mins / 2)) - ( sleep "${half}m"; kill -HUP "$(cat "$ARTIFACT_DIR/monitor.pid" 2>/dev/null)" 2>/dev/null ) & + ( sleep "${half}m"; kill -HUP "$(cat "$ARTIFACT_DIR/workstation-probe.pid" 2>/dev/null)" 2>/dev/null ) & fi fi diff --git a/webview/README.md b/webview/README.md index 6a61b1f..e21e817 100644 --- a/webview/README.md +++ b/webview/README.md @@ -1,59 +1,20 @@ # webview/ -Webview frontends for `workstation-probe`. Each subdirectory is a -self-contained integration targeting one site-framework. The convention -is one subdirectory per framework so each can be versioned and published -independently. +Webview integrations for `workstation-probe`. The active integration is +the Hugo module in `hugo/`. ## Layout | Subdirectory | Status | Purpose | |---|---|---| | `hugo/` | active | Hugo Module — embed panels via the `workstation-probe-panel` shortcode | -| `hugo/test/` | active | Snapshot tests for `hugo/` AND the local dev site. One site serves both purposes — see below. | -| `astro/` | planned | Astro component (future) | -| `nextjs/` | planned | Next.js component (future) | ## Conventions -- Each `/` is independent: own `README.md`, own package/module - manifest, own source tree. +- Each integration owns its own `README.md`, package/module manifest, and + source tree. - Common product name (`workstation-probe`) and common API contract (`/metrics`, `/metrics/`, `/metrics//history`) are - shared across frameworks — only the embed mechanism differs. + shared across integrations. -## Hugo Module — development & testing - -`hugo/exampleSite/basic/` is the canonical site for both: - -- **Dev preview** — `hugo server -D` for live iteration against the - module. Any change in `hugo/layouts/` or `hugo/static/` is picked up - immediately because `hugo/exampleSite/basic/go.mod` points the module - import at `../..` (the hugo module's own directory). -- **Snapshot test** — `webview/hugo/test/run.sh` builds the same site - and diffs against `webview/hugo/test/expected/basic/`. Catches - byte-level regressions in module output. - -Because both flows use the same site, the goldens always reflect the -content the dev server renders. - -To develop: - -```bash -cd webview/hugo/exampleSite/basic -hugo server -D -# Open http://localhost:1313 — pages render through the module's shortcode. -``` - -To run the snapshot test: - -```bash -cd webview/hugo/test -./run.sh # verify against goldens -./run.sh --update # overwrite goldens after an intentional change -``` - -The site resolves the `workstation-probe` Hugo module via a relative -path in its `go.mod` (`replace ... => ../..`). No absolute path is -stored anywhere in the repo, so cloning to a different home directory -needs no edits. +For Hugo usage and module-development details, see `hugo/README.md`. diff --git a/webview/hugo/README.md b/webview/hugo/README.md index 20f06f2..a3a7108 100644 --- a/webview/hugo/README.md +++ b/webview/hugo/README.md @@ -27,7 +27,7 @@ It does **not** ship a `content/` or `configs/` directory; those are the caller's responsibility. For local development of this module itself, see -[`../exampleSite/basic/`](../../exampleSite/basic/). The same site is +[`exampleSite/basic/`](exampleSite/basic/). The same site is also the snapshot-test target — see `test/README.md`. ## Namespace @@ -74,7 +74,7 @@ site-wide scripts), include the panel frontend: {{ partial "workstation-probe/scripts.html" . }} ``` -For a starting `baseof.html`, copy from `../../exampleSite/basic/layouts/_default/baseof.html`. +For a starting `baseof.html`, copy from `exampleSite/basic/layouts/_default/baseof.html`. ## Usage @@ -110,10 +110,10 @@ content/posts/other.md ← plain file ```yaml # in any of: page resource, configs/.yaml -name: rig-01 # shown in the panel header -api: "http://localhost:19090" # monitor base URL (no trailing slash) -refresh: "5s" # poll interval — Go duration: 500ms, 5s, 2m -modules: # which sub-panels to render +name: rig-01 # shown in the panel header +api: "http://localhost:19090" # workstation-probe server base URL (no trailing slash) +refresh: "5s" # poll interval — Go duration: 500ms, 5s, 2m +modules: # which sub-panels to render - cpu - memory - gpu @@ -132,9 +132,9 @@ Same for URLs with colons. ## CORS `workstation-probe`'s default config does not enable CORS. When this site -runs on a different origin than the monitor (e.g. dev server on -`http://localhost:1313` and monitor on `http://localhost:19090`), configure -the monitor's CORS section: +runs on a different origin than the `workstation-probe` server (e.g. dev +server on `http://localhost:1313` and the API on `http://localhost:19090`), +configure the server's CORS section: ```yaml security: @@ -145,19 +145,7 @@ security: ``` For a single-origin deploy (Hugo served behind the same reverse proxy as -the monitor), this is unnecessary. - -## Intranet NAS mirror - -For a public GitHub Pages blog plus a LAN-only live dashboard, do not embed -the panel on the public page. Link from the public page to a NAS-hosted Hugo -mirror, then render `workstation-probe-panel` only on the NAS page. This keeps -the browser from fetching private HTTP APIs from a public HTTPS origin. - -The repository includes `nas-overlay.example/`, a template for the private -NAS overlay that adds an intranet monitor article and per-workstation YAML -configs. The full deployment model is documented in -`../../docs/intranet-mirror.md`. +the `workstation-probe` server), this is unnecessary. ## Architecture diff --git a/webview/hugo/hugo.yaml b/webview/hugo/hugo.yaml index 5c6c584..548d036 100644 --- a/webview/hugo/hugo.yaml +++ b/webview/hugo/hugo.yaml @@ -3,6 +3,6 @@ # languages, etc. in their own hugo.yaml. name: workstation-probe-hugo -description: Embed live workstation-probe dashboards in Hugo markdown via the `panel` shortcode. +description: Embed live workstation-probe dashboards in Hugo markdown via the `workstation-probe-panel` shortcode. license: MIT -minimumHugoVersion: 0.158.0 \ No newline at end of file +minimumHugoVersion: 0.158.0 diff --git a/webview/hugo/nas-overlay.example/README.md b/webview/hugo/nas-overlay.example/README.md deleted file mode 100644 index b2169c0..0000000 --- a/webview/hugo/nas-overlay.example/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# NAS overlay example - -This directory is a template for a private intranet overlay. Copy it to the -NAS, edit the placeholder hostnames/IPs, and apply it on top of a clean clone -before running `hugo --environment intranet`. - -Example NAS-side location: - -```text -/volume1/lab-blog/overlay/ -``` - -Files: - -- `config/intranet/hugo.yaml` overrides `baseURL` for the NAS mirror. -- `content/posts/workstation-monitor/index.md` is the intranet article. -- `content/posts/workstation-monitor/ws01.yaml` and `ws02.yaml` are sample - panel configs. - -The real overlay should not be committed if it contains lab-specific hostnames, -IP addresses, tokens, or any other private topology. - -See `docs/intranet-mirror.md` from the repository root for the full deployment -model. diff --git a/webview/hugo/nas-overlay.example/config/intranet/hugo.yaml b/webview/hugo/nas-overlay.example/config/intranet/hugo.yaml deleted file mode 100644 index b92de34..0000000 --- a/webview/hugo/nas-overlay.example/config/intranet/hugo.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Replace with the URL served by the NAS mirror. -baseURL: "http://nas-host-or-ip/lab-blog/" diff --git a/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/index.md b/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/index.md deleted file mode 100644 index 73f570a..0000000 --- a/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "Workstation monitor" -date: 2026-07-08 ---- - -{{< workstation-probe-panel "./ws01.yaml" >}} - -{{< workstation-probe-panel "./ws02.yaml" >}} diff --git a/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/ws01.yaml b/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/ws01.yaml deleted file mode 100644 index 6b71124..0000000 --- a/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/ws01.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: ws01 -api: "http://192.168.1.101:19090" -refresh: "30s" -modules: - - cpu - - memory - - gpu - - storage diff --git a/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/ws02.yaml b/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/ws02.yaml deleted file mode 100644 index 49fbca4..0000000 --- a/webview/hugo/nas-overlay.example/content/posts/workstation-monitor/ws02.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: ws02 -api: "http://192.168.1.102:19090" -refresh: "30s" -modules: - - cpu - - memory - - gpu - - storage diff --git a/webview/hugo/test/README.md b/webview/hugo/test/README.md index 9ece498..fe321e5 100644 --- a/webview/hugo/test/README.md +++ b/webview/hugo/test/README.md @@ -78,10 +78,10 @@ local CPU, memory, GPU, and storage panels. CORS is enabled on the dev probe server, so the cross-origin fetch from `localhost:1313` to `localhost:19090` works. -`scripts/dev-server.sh` defaults to `GPU=auto`: it rebuilds `./monitor` -before starting, uses the `nvml` build tag when `libnvidia-ml.so` is -available, and falls back to the stub GPU collector otherwise. Force a -mode with `GPU=nvml ./scripts/dev-server.sh` or +`scripts/dev-server.sh` defaults to `GPU=auto`: it rebuilds the local +development server binary before starting, uses the `nvml` build tag when +`libnvidia-ml.so` is available, and falls back to the stub GPU collector +otherwise. Force a mode with `GPU=nvml ./scripts/dev-server.sh` or `GPU=stub ./scripts/dev-server.sh`. ## Layout