openvibers/openvibe.host Commits
Developer path against production, daily (WS-N task 1): openvibe-devpath.timer runs OpenVibe.Examples' developer path as an unprivileged user with a dedicated test account from a root-only env file, writes step outcomes to /var/lib/openvibe-devpath/last.json and metrics for OpenVibeDeveloperPathFailed/Missed
Changes
@@@ -51,3 +51,16 @@ groups:labels: { severity: ticket }annotations:summary: "{{ $labels.service }}: main has had commits production does not run for more than a day"++ # The developer path against production, daily (openvibe-devpath.timer; WS-N task 1).++ - alert: OpenVibeDeveloperPathFailed++ expr: openvibe_devpath_last_run_ok == 0++ for: 5m++ labels: { severity: ticket }++ annotations:++ summary: "The daily developer path failed ({{ with query \"openvibe_devpath_steps_failed\" }}{{ . | first | value }}{{ end }} step(s)): a developer cannot go from an account to Media and Events"++ - alert: OpenVibeDeveloperPathMissed++ expr: time() - openvibe_devpath_last_success_timestamp_seconds > 30 * 3600++ for: 30m++ labels: { severity: ticket }++ annotations:++ summary: "No passing developer-path run for more than 30 hours"nnew file mode 100644@@@ -0,0 +1,23 @@++[Unit]++Description=OpenVibe developer path against production (roadmap WS-N task 1): a test account's sandbox project, Media and Events through the SDK, credential rotation and revocation, cleanup++Documentation=https://github.com/OpenVibers/OpenVibe.Host/blob/main/docs/developer-path.md++After=network-online.target++Wants=network-online.target++++[Service]++Type=oneshot++# The account's name and password come from a root-only env file that systemd reads before dropping to++# the unprivileged user; the script never prints them (masked output, and --result holds step names only).++User=ubuntu++EnvironmentFile=/etc/openvibe/devpath.env++Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin++WorkingDirectory=/usr/local/lib/openvibe-examples++StateDirectory=openvibe-devpath++StateDirectoryMode=0755++ExecStart=/usr/bin/env node scripts/developer-path.js --result /var/lib/openvibe-devpath/last.json++# As root: the outcome for Prometheus (node_exporter's textfile collector), whatever the exit code.++ExecStopPost=+/usr/local/lib/openvibe-host/scripts/devpath-metrics.sh++TimeoutStartSec=15min++NoNewPrivileges=true++PrivateTmp=true++ProtectSystem=fullnnew file mode 100644@@@ -0,0 +1,10 @@++[Unit]++Description=Run the OpenVibe developer path against production daily (WS-N task 1)++++[Timer]++OnCalendar=*-*-* 05:10:00 UTC++RandomizedDelaySec=15min++Persistent=true++++[Install]++WantedBy=timers.targetnnew file mode 100644@@@ -0,0 +1,28 @@++# The developer path against production, daily (roadmap WS-N task 1)++++`openvibe-devpath.timer` runs OpenVibe.Examples' `scripts/developer-path.js` once a day (05:10 UTC, up to++15 minutes later) against production, as an ordinary developer would: sign in with a dedicated test++account, create a sandbox project and app, get grants, upload and read a file through Media, publish and++pull an event through Events, rotate and revoke the credential, archive the project.++++| Piece | Where |++|---|---|++| Checkout | `/usr/local/lib/openvibe-examples` (root-owned; `git pull` + `npm ci` to update) |++| Account | `/etc/openvibe/devpath.env` (root, 0600): `OV_E2E_USERNAME`, `OV_E2E_PASSWORD` — never printed |++| Units | `deploy/systemd/openvibe-devpath.service` + `.timer` (runs as `ubuntu`) |++| Result | `/var/lib/openvibe-devpath/last.json`: step names and ok only (shown on openvibe.network/status) |++| Metrics | `scripts/devpath-metrics.sh` → node_exporter textfile `openvibe_devpath.prom` |++| Alerts | `OpenVibeDeveloperPathFailed`, `OpenVibeDeveloperPathMissed` (30 h) in `deploy/prometheus/openvibe-rules.yml` |++++Install (once, as root):++++```sh++git clone https://github.com/OpenVibers/OpenVibe.Examples /usr/local/lib/openvibe-examples && (cd /usr/local/lib/openvibe-examples && npm ci --no-audit --no-fund)++umask 077; printf 'OV_E2E_USERNAME=ovprobe_devpath\nOV_E2E_PASSWORD=%s\n' "$(openssl rand -hex 24)" > /etc/openvibe/devpath.env++install -m 644 deploy/systemd/openvibe-devpath.{service,timer} /etc/systemd/system/ && systemctl daemon-reload++# first run registers the account:++set -a; . /etc/openvibe/devpath.env; set +a; runuser -u ubuntu -p -- node /usr/local/lib/openvibe-examples/scripts/developer-path.js --register++systemctl enable --now openvibe-devpath.timer++```++++Run now: `systemctl start openvibe-devpath.service`; read: `journalctl -u openvibe-devpath` (masked).nnew file mode 100755@@@ -0,0 +1,34 @@++#!/usr/bin/env bash++# The developer path's outcome for Prometheus (openvibe-devpath.service ExecStopPost, as root; WS-N task 1):++# node_exporter's textfile collector reads /var/lib/prometheus/node-exporter/openvibe_devpath.prom.++# openvibe_devpath_last_run_timestamp_seconds, openvibe_devpath_last_run_ok,++# openvibe_devpath_last_success_timestamp_seconds (kept from the previous file when this run failed),++# openvibe_devpath_steps_failed++# A run that refused to start or wrote no result (exit != 0, no fresh last.json) counts as failed.++set -euo pipefail++RESULT=${DEVPATH_RESULT:-/var/lib/openvibe-devpath/last.json}++DIR=${TEXTFILE_DIR:-/var/lib/prometheus/node-exporter}++[ -d "$DIR" ] || exit 0++OUT="$DIR/openvibe_devpath.prom"++node - "$RESULT" "$OUT" "${EXIT_STATUS:-}" <<'JS'++const fs = require('fs');++const [file, out, exitStatus] = process.argv.slice(2);++const now = Math.floor(Date.now() / 1000);++let rec = null;++try { rec = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { rec = null; }++const fresh = rec && Date.parse(rec.finished_at) / 1000 > now - 3600;++const ok = !!(fresh && rec.ok && (exitStatus === '' || exitStatus === '0'));++let lastSuccess = null;++try { const m = /^openvibe_devpath_last_success_timestamp_seconds (\d+)$/m.exec(fs.readFileSync(out, 'utf8')); if (m) lastSuccess = Number(m[1]); } catch { /* first run */ }++if (ok) lastSuccess = Math.floor(Date.parse(rec.finished_at) / 1000);++const failed = fresh ? rec.steps.filter((s) => !s.ok && !s.skipped).length : 1;++const lines = [++ '# HELP openvibe_devpath_last_run_timestamp_seconds When the last developer-path run ended.',++ '# TYPE openvibe_devpath_last_run_timestamp_seconds gauge', `openvibe_devpath_last_run_timestamp_seconds ${fresh ? Math.floor(Date.parse(rec.finished_at) / 1000) : now}`,++ '# HELP openvibe_devpath_last_run_ok 1 when every step passed.', '# TYPE openvibe_devpath_last_run_ok gauge', `openvibe_devpath_last_run_ok ${ok ? 1 : 0}`,++ '# HELP openvibe_devpath_steps_failed Steps that failed in the last run.', '# TYPE openvibe_devpath_steps_failed gauge', `openvibe_devpath_steps_failed ${failed}`,++];++if (lastSuccess) lines.push('# HELP openvibe_devpath_last_success_timestamp_seconds When the last fully passing run ended.', '# TYPE openvibe_devpath_last_success_timestamp_seconds gauge', `openvibe_devpath_last_success_timestamp_seconds ${lastSuccess}`);++fs.writeFileSync(`${out}.tmp`, lines.join('\n') + '\n', { mode: 0o644 });++fs.renameSync(`${out}.tmp`, out);++JS
@@ -51,3 +51,16 @@ groups:
@@ -51,3 +51,16 @@ groups:
labels: { severity: ticket }
labels: { severity: ticket }
annotations:
annotations:
summary: "{{ $labels.service }}: main has had commits production does not run for more than a day"
summary: "{{ $labels.service }}: main has had commits production does not run for more than a day"
+ # The developer path against production, daily (openvibe-devpath.timer; WS-N task 1).
+ - alert: OpenVibeDeveloperPathFailed
+ expr: openvibe_devpath_last_run_ok == 0
+ for: 5m
+ labels: { severity: ticket }
+ annotations:
+ summary: "The daily developer path failed ({{ with query \"openvibe_devpath_steps_failed\" }}{{ . | first | value }}{{ end }} step(s)): a developer cannot go from an account to Media and Events"
+ - alert: OpenVibeDeveloperPathMissed
+ expr: time() - openvibe_devpath_last_success_timestamp_seconds > 30 * 3600
+ for: 30m
+ labels: { severity: ticket }
+ annotations:
+ summary: "No passing developer-path run for more than 30 hours"
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
+[Unit]
+Description=OpenVibe developer path against production (roadmap WS-N task 1): a test account's sandbox project, Media and Events through the SDK, credential rotation and revocation, cleanup
+Documentation=https://github.com/OpenVibers/OpenVibe.Host/blob/main/docs/developer-path.md
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=oneshot
+# The account's name and password come from a root-only env file that systemd reads before dropping to
+# the unprivileged user; the script never prints them (masked output, and --result holds step names only).
+User=ubuntu
+EnvironmentFile=/etc/openvibe/devpath.env
+Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+WorkingDirectory=/usr/local/lib/openvibe-examples
+StateDirectory=openvibe-devpath
+StateDirectoryMode=0755
+ExecStart=/usr/bin/env node scripts/developer-path.js --result /var/lib/openvibe-devpath/last.json
+# As root: the outcome for Prometheus (node_exporter's textfile collector), whatever the exit code.
+ExecStopPost=+/usr/local/lib/openvibe-host/scripts/devpath-metrics.sh
+TimeoutStartSec=15min
+NoNewPrivileges=true
+PrivateTmp=true
+ProtectSystem=full
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+[Unit]
+Description=Run the OpenVibe developer path against production daily (WS-N task 1)
+
+[Timer]
+OnCalendar=*-*-* 05:10:00 UTC
+RandomizedDelaySec=15min
+Persistent=true
+
+[Install]
+WantedBy=timers.target
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+# The developer path against production, daily (roadmap WS-N task 1)
+
+`openvibe-devpath.timer` runs OpenVibe.Examples' `scripts/developer-path.js` once a day (05:10 UTC, up to
+15 minutes later) against production, as an ordinary developer would: sign in with a dedicated test
+account, create a sandbox project and app, get grants, upload and read a file through Media, publish and
+pull an event through Events, rotate and revoke the credential, archive the project.
+
+| Piece | Where |
+|---|---|
+| Checkout | `/usr/local/lib/openvibe-examples` (root-owned; `git pull` + `npm ci` to update) |
+| Account | `/etc/openvibe/devpath.env` (root, 0600): `OV_E2E_USERNAME`, `OV_E2E_PASSWORD` — never printed |
+| Units | `deploy/systemd/openvibe-devpath.service` + `.timer` (runs as `ubuntu`) |
+| Result | `/var/lib/openvibe-devpath/last.json`: step names and ok only (shown on openvibe.network/status) |
+| Metrics | `scripts/devpath-metrics.sh` → node_exporter textfile `openvibe_devpath.prom` |
+| Alerts | `OpenVibeDeveloperPathFailed`, `OpenVibeDeveloperPathMissed` (30 h) in `deploy/prometheus/openvibe-rules.yml` |
+
+Install (once, as root):
+
+```sh
+git clone https://github.com/OpenVibers/OpenVibe.Examples /usr/local/lib/openvibe-examples && (cd /usr/local/lib/openvibe-examples && npm ci --no-audit --no-fund)
+umask 077; printf 'OV_E2E_USERNAME=ovprobe_devpath\nOV_E2E_PASSWORD=%s\n' "$(openssl rand -hex 24)" > /etc/openvibe/devpath.env
+install -m 644 deploy/systemd/openvibe-devpath.{service,timer} /etc/systemd/system/ && systemctl daemon-reload
+# first run registers the account:
+set -a; . /etc/openvibe/devpath.env; set +a; runuser -u ubuntu -p -- node /usr/local/lib/openvibe-examples/scripts/developer-path.js --register
+systemctl enable --now openvibe-devpath.timer
+```
+
+Run now: `systemctl start openvibe-devpath.service`; read: `journalctl -u openvibe-devpath` (masked).
new file mode 100755
new file mode 100755
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# The developer path's outcome for Prometheus (openvibe-devpath.service ExecStopPost, as root; WS-N task 1):
+# node_exporter's textfile collector reads /var/lib/prometheus/node-exporter/openvibe_devpath.prom.
+# openvibe_devpath_last_run_timestamp_seconds, openvibe_devpath_last_run_ok,
+# openvibe_devpath_last_success_timestamp_seconds (kept from the previous file when this run failed),
+# openvibe_devpath_steps_failed
+# A run that refused to start or wrote no result (exit != 0, no fresh last.json) counts as failed.
+set -euo pipefail
+RESULT=${DEVPATH_RESULT:-/var/lib/openvibe-devpath/last.json}
+DIR=${TEXTFILE_DIR:-/var/lib/prometheus/node-exporter}
+[ -d "$DIR" ] || exit 0
+OUT="$DIR/openvibe_devpath.prom"
+node - "$RESULT" "$OUT" "${EXIT_STATUS:-}" <<'JS'
+const fs = require('fs');
+const [file, out, exitStatus] = process.argv.slice(2);
+const now = Math.floor(Date.now() / 1000);
+let rec = null;
+try { rec = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { rec = null; }
+const fresh = rec && Date.parse(rec.finished_at) / 1000 > now - 3600;
+const ok = !!(fresh && rec.ok && (exitStatus === '' || exitStatus === '0'));
+let lastSuccess = null;
+try { const m = /^openvibe_devpath_last_success_timestamp_seconds (\d+)$/m.exec(fs.readFileSync(out, 'utf8')); if (m) lastSuccess = Number(m[1]); } catch { /* first run */ }
+if (ok) lastSuccess = Math.floor(Date.parse(rec.finished_at) / 1000);
+const failed = fresh ? rec.steps.filter((s) => !s.ok && !s.skipped).length : 1;
+const lines = [
+ '# HELP openvibe_devpath_last_run_timestamp_seconds When the last developer-path run ended.',
+ '# TYPE openvibe_devpath_last_run_timestamp_seconds gauge', `openvibe_devpath_last_run_timestamp_seconds ${fresh ? Math.floor(Date.parse(rec.finished_at) / 1000) : now}`,
+ '# HELP openvibe_devpath_last_run_ok 1 when every step passed.', '# TYPE openvibe_devpath_last_run_ok gauge', `openvibe_devpath_last_run_ok ${ok ? 1 : 0}`,
+ '# HELP openvibe_devpath_steps_failed Steps that failed in the last run.', '# TYPE openvibe_devpath_steps_failed gauge', `openvibe_devpath_steps_failed ${failed}`,
+];
+if (lastSuccess) lines.push('# HELP openvibe_devpath_last_success_timestamp_seconds When the last fully passing run ended.', '# TYPE openvibe_devpath_last_success_timestamp_seconds gauge', `openvibe_devpath_last_success_timestamp_seconds ${lastSuccess}`);
+fs.writeFileSync(`${out}.tmp`, lines.join('\n') + '\n', { mode: 0o644 });
+fs.renameSync(`${out}.tmp`, out);
+JS