Integrate reviewed getting-started safety and retirement guidance

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot] 2026-10-02 10:48:57 +01:00
commit 5635f74043
7 changed files with 276 additions and 41 deletions

View file

@ -9,6 +9,7 @@ on:
- ".github/ISSUE_TEMPLATE/**"
- "scripts/check_public_docs.py"
- "scripts/check_docs_mirror.py"
- "scripts/readme_entrypoints_test.py"
- ".github/workflows/public-docs.yml"
push:
branches:
@ -20,6 +21,7 @@ on:
- ".github/ISSUE_TEMPLATE/**"
- "scripts/check_public_docs.py"
- "scripts/check_docs_mirror.py"
- "scripts/readme_entrypoints_test.py"
- ".github/workflows/public-docs.yml"
permissions:
@ -42,5 +44,8 @@ jobs:
- name: Validate public documentation
run: python3 scripts/check_public_docs.py
- name: Check getting-started safety and current plans
run: python3 scripts/readme_entrypoints_test.py
- name: Check shipped docs mirror sync
run: python3 scripts/check_docs_mirror.py

View file

@ -91,17 +91,19 @@ The installer is signed. Verify `install.sh` against the pinned
```bash
export PULSE_VERSION=vX.Y.Z
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" &&
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" &&
ssh-keygen -Y verify \
-f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
-I pulse-installer \
-n pulse-install \
-s install.sh.sshsig < install.sh
-s install.sh.sshsig < install.sh &&
bash install.sh --version "${PULSE_VERSION}"
rm -f install.sh install.sh.sshsig
```
Paste the whole block: installation stops if either download or signature
verification fails. Do not run the final command separately to bypass a failure.
The GitHub installer installs the Pulse server. Install and upgrade agents
(including v5-to-v6 agent upgrades) with the per-host command generated under
**Settings → Infrastructure → Install on a host**.
@ -133,15 +135,19 @@ agent can and cannot do at each privilege level.
- **Community** — self-hosted monitoring, seven days of metric history, core
SSO, update alerts, and Patrol with your own provider or local model.
- **Relay** — Community plus secure remote web access, Pulse Mobile pairing,
push notifications, and fourteen days of history.
- **Pro** — Relay plus Patrol investigation, governed fixes, ninety days of
- **Pro** — Community plus Patrol investigation, governed fixes, ninety days of
history, centralized agent profiles, RBAC, audit logging, and reporting.
- **MSP** — for managed service providers: one Pulse Account running many
client workspaces, each with an isolated Pulse runtime — separate
dashboards, alerts, users, audit history, and reports. Free sixty-day
two-client evaluation at [Pulse for MSPs](https://pulserelay.pro/msp).
Relay is no longer sold. Existing Relay subscribers receive Pro features at their
current price for as long as their subscription continues. Existing paired phones
keep working until **31 March 2027**. Relay connects the app, not the web UI. Use
your own VPN or tunnel for remote web access. For phone alerts, add an
ntfy, Gotify or Pushover destination and open Pulse in your phone's browser.
Core self-hosted monitoring is not gated by monitored-system or child-resource
volume. See the [runtime-aligned capability reference](docs/PULSE_PRO.md) and
[current plans](https://pulserelay.pro) for details.

View file

@ -10,10 +10,17 @@ absichtlich unverändert.
Pulse ist ein self-hosted Monitoring-Arbeitsbereich für Proxmox, Docker,
Kubernetes, TrueNAS und verwandte Infrastruktur. Community deckt das
Kernmonitoring kostenlos ab. Relay ergänzt sicheren Remote-Zugriff auf die
Pulse-Weboberfläche, Pulse-Mobile-Handoff-Pairing, Push-Benachrichtigungen und
14 Tage Verlauf. Pro ergänzt Ursachenanalyse, sichere Remediation-Workflows,
Operations-Werkzeuge, Governance-Funktionen und 90 Tage Verlauf.
Kernmonitoring kostenlos ab. Pro ergänzt Ursachenanalyse, sichere
Remediation-Workflows, Operations-Werkzeuge, Governance-Funktionen und 90 Tage
Verlauf.
Relay wird nicht mehr verkauft. Bestehende Relay-Abonnenten erhalten
Pro-Funktionen zum bisherigen Preis, solange ihr Abonnement weiterläuft.
Bereits gekoppelte Telefone funktionieren bis zum **31. März 2027** weiter.
Relay verbindet Pulse mit der App, nicht mit der Weboberfläche. Für den
Remote-Zugriff auf die Weboberfläche nutze ein eigenes VPN oder einen Tunnel.
Für Warnmeldungen auf dem Telefon füge ein Ziel für ntfy, Gotify oder Pushover
hinzu und öffne Pulse im Browser deines Telefons.
## Bezahlte Relay-, Pro- und Legacy-Kunden
@ -37,17 +44,20 @@ Installer-Datei und führe den Installer auf deinem Proxmox-Host aus:
```bash
export PULSE_VERSION=vX.Y.Z
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" &&
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" &&
ssh-keygen -Y verify \
-f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
-I pulse-installer \
-n pulse-install \
-s install.sh.sshsig < install.sh
-s install.sh.sshsig < install.sh &&
bash install.sh --version "${PULSE_VERSION}"
rm -f install.sh install.sh.sshsig
```
Füge den gesamten Block ein: Schlägt ein Download oder die Signaturprüfung fehl,
wird der Installer nicht ausgeführt. Führe den letzten Befehl nicht separat aus,
um einen Fehler zu umgehen.
Agent-Installationen und v5-zu-v6-Agent-Upgrades verwenden den Befehl, den
Pulse unter **Settings → Infrastructure → Install on a host** erzeugt. Dieser
Befehl wird von deinem Pulse-Server über `/install.sh` bereitgestellt.

View file

@ -10,11 +10,16 @@ la interfaz se conservan sin traducir de forma intencional.
Pulse es un workspace de monitoreo autohospedado para Proxmox, Docker,
Kubernetes, TrueNAS e infraestructura relacionada. Community incluye el
monitoreo principal gratis. Relay añade acceso remoto seguro a la interfaz web
de Pulse, emparejamiento con Pulse Mobile para handoff, notificaciones push e
historial de 14 días. Pro añade análisis de causa raíz, flujos de remediación
seguros, herramientas operativas, funciones de gobernanza e historial de 90
días.
monitoreo principal gratis. Pro añade análisis de causa raíz, flujos de
remediación seguros, herramientas operativas, funciones de gobernanza e
historial de 90 días.
Relay ya no se vende. Los suscriptores actuales de Relay reciben funciones Pro
al precio actual mientras continúe su suscripción. Los teléfonos ya emparejados
siguen funcionando hasta el **31 de marzo de 2027**. Relay conecta la app,
no la interfaz web. Para acceder a la interfaz web fuera de casa, usa tu propia
VPN o un túnel. Para alertas en el teléfono, añade un destino de ntfy, Gotify o
Pushover y abre Pulse en el navegador de tu teléfono.
## Clientes de pago Relay, Pro y legacy
@ -37,17 +42,20 @@ host Proxmox:
```bash
export PULSE_VERSION=vX.Y.Z
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" &&
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" &&
ssh-keygen -Y verify \
-f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
-I pulse-installer \
-n pulse-install \
-s install.sh.sshsig < install.sh
-s install.sh.sshsig < install.sh &&
bash install.sh --version "${PULSE_VERSION}"
rm -f install.sh install.sh.sshsig
```
Pega el bloque completo: si falla una descarga o la verificación de firma, el
instalador no se ejecuta. No ejecutes el último comando por separado para omitir
un fallo.
Las instalaciones de agentes y las actualizaciones de agentes de v5 a v6 usan
el comando que Pulse genera en **Settings → Infrastructure → Install on a
host**. Ese comando se sirve desde tu servidor Pulse en `/install.sh`.

View file

@ -10,10 +10,17 @@ absichtlich unverändert.
Pulse ist ein self-hosted Monitoring-Arbeitsbereich für Proxmox, Docker,
Kubernetes, TrueNAS und verwandte Infrastruktur. Community deckt das
Kernmonitoring kostenlos ab. Relay ergänzt sicheren Remote-Zugriff auf die
Pulse-Weboberfläche, Pulse-Mobile-Handoff-Pairing, Push-Benachrichtigungen und
14 Tage Verlauf. Pro ergänzt Ursachenanalyse, sichere Remediation-Workflows,
Operations-Werkzeuge, Governance-Funktionen und 90 Tage Verlauf.
Kernmonitoring kostenlos ab. Pro ergänzt Ursachenanalyse, sichere
Remediation-Workflows, Operations-Werkzeuge, Governance-Funktionen und 90 Tage
Verlauf.
Relay wird nicht mehr verkauft. Bestehende Relay-Abonnenten erhalten
Pro-Funktionen zum bisherigen Preis, solange ihr Abonnement weiterläuft.
Bereits gekoppelte Telefone funktionieren bis zum **31. März 2027** weiter.
Relay verbindet Pulse mit der App, nicht mit der Weboberfläche. Für den
Remote-Zugriff auf die Weboberfläche nutze ein eigenes VPN oder einen Tunnel.
Für Warnmeldungen auf dem Telefon füge ein Ziel für ntfy, Gotify oder Pushover
hinzu und öffne Pulse im Browser deines Telefons.
## Bezahlte Relay-, Pro- und Legacy-Kunden
@ -37,17 +44,20 @@ Installer-Datei und führe den Installer auf deinem Proxmox-Host aus:
```bash
export PULSE_VERSION=vX.Y.Z
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" &&
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" &&
ssh-keygen -Y verify \
-f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
-I pulse-installer \
-n pulse-install \
-s install.sh.sshsig < install.sh
-s install.sh.sshsig < install.sh &&
bash install.sh --version "${PULSE_VERSION}"
rm -f install.sh install.sh.sshsig
```
Füge den gesamten Block ein: Schlägt ein Download oder die Signaturprüfung fehl,
wird der Installer nicht ausgeführt. Führe den letzten Befehl nicht separat aus,
um einen Fehler zu umgehen.
Agent-Installationen und v5-zu-v6-Agent-Upgrades verwenden den Befehl, den
Pulse unter **Settings → Infrastructure → Install on a host** erzeugt. Dieser
Befehl wird von deinem Pulse-Server über `/install.sh` bereitgestellt.

View file

@ -10,11 +10,16 @@ la interfaz se conservan sin traducir de forma intencional.
Pulse es un workspace de monitoreo autohospedado para Proxmox, Docker,
Kubernetes, TrueNAS e infraestructura relacionada. Community incluye el
monitoreo principal gratis. Relay añade acceso remoto seguro a la interfaz web
de Pulse, emparejamiento con Pulse Mobile para handoff, notificaciones push e
historial de 14 días. Pro añade análisis de causa raíz, flujos de remediación
seguros, herramientas operativas, funciones de gobernanza e historial de 90
días.
monitoreo principal gratis. Pro añade análisis de causa raíz, flujos de
remediación seguros, herramientas operativas, funciones de gobernanza e
historial de 90 días.
Relay ya no se vende. Los suscriptores actuales de Relay reciben funciones Pro
al precio actual mientras continúe su suscripción. Los teléfonos ya emparejados
siguen funcionando hasta el **31 de marzo de 2027**. Relay conecta la app,
no la interfaz web. Para acceder a la interfaz web fuera de casa, usa tu propia
VPN o un túnel. Para alertas en el teléfono, añade un destino de ntfy, Gotify o
Pushover y abre Pulse en el navegador de tu teléfono.
## Clientes de pago Relay, Pro y legacy
@ -37,17 +42,20 @@ host Proxmox:
```bash
export PULSE_VERSION=vX.Y.Z
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig"
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh" &&
curl -fsSLO "https://github.com/rcourtman/Pulse/releases/download/${PULSE_VERSION}/install.sh.sshsig" &&
ssh-keygen -Y verify \
-f <(printf '%s\n' 'pulse-installer namespaces="pulse-install" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer') \
-I pulse-installer \
-n pulse-install \
-s install.sh.sshsig < install.sh
-s install.sh.sshsig < install.sh &&
bash install.sh --version "${PULSE_VERSION}"
rm -f install.sh install.sh.sshsig
```
Pega el bloque completo: si falla una descarga o la verificación de firma, el
instalador no se ejecuta. No ejecutes el último comando por separado para omitir
un fallo.
Las instalaciones de agentes y las actualizaciones de agentes de v5 a v6 usan
el comando que Pulse genera en **Settings → Infrastructure → Install on a
host**. Ese comando se sirve desde tu servidor Pulse en `/install.sh`.

View file

@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Exercise the public landing pages' signed-install gate without network access."""
from __future__ import annotations
import json
import re
import subprocess
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def read_repo_text(relative_path: str) -> str:
return (ROOT / relative_path).read_text(encoding="utf-8")
ENTRYPOINTS = (
"README.md",
"docs/i18n/de/README.md",
"docs/i18n/es/README.md",
)
SIGNER = (
'pulse-installer namespaces="pulse-install" ssh-ed25519 '
"AAAAC3NzaC1lZDI1NTE5AAAAIMZd/DaH+BldzOkq1A8KVTcFk73nAyrE8aJOyf7i00jm pulse-installer"
)
def installer_commands(text: str) -> str:
for block in re.findall(r"```bash\n(.*?)\n```", text, flags=re.DOTALL):
if "ssh-keygen -Y verify" not in block:
continue
lines = block.splitlines()
for index, line in enumerate(lines):
if line.startswith("bash install.sh --version "):
# Only exercise the download -> verification -> execution gate,
# never any subsequent file-cleanup recipe.
return "\n".join(lines[: index + 1]) + "\n"
raise AssertionError("signed server-installer command block is missing")
STUB = """#!/usr/bin/python3
import json
import os
import sys
from pathlib import Path
name = Path(sys.argv[0]).name
trace = Path(os.environ['TRACE'])
events = [json.loads(line) for line in trace.read_text().splitlines()] if trace.exists() else []
event = {'command': name, 'argv': sys.argv[1:]}
if name == 'ssh-keygen':
event['allowed_signers'] = Path(sys.argv[sys.argv.index('-f') + 1]).read_text()
with trace.open('a') as out:
out.write(json.dumps(event) + '\\n')
if name == 'curl':
attempt = 1 + sum(event['command'] == 'curl' for event in events)
code = int(os.environ.get('CURL_' + str(attempt), '0'))
if code == 0:
# Public dummy bytes only. The installer itself is never executed.
Path(sys.argv[-1].rsplit('/', 1)[-1]).write_text('downloaded fixture\\n')
elif name == 'ssh-keygen':
sys.stdin.read()
code = int(os.environ.get('VERIFY', '0'))
elif name == 'bash':
code = int(os.environ.get('INSTALL', '0'))
else:
raise AssertionError('unexpected stub command')
sys.exit(code)
"""
def exercise_install(commands: str, exits: dict[str, str]) -> tuple[int, list[dict]]:
with tempfile.TemporaryDirectory(prefix="pulse-readme-gate-") as directory:
root = Path(directory)
stubs = root / "bin"
stubs.mkdir()
for name in ("curl", "ssh-keygen", "bash"):
stub = stubs / name
stub.write_text(STUB, encoding="utf-8")
stub.chmod(0o700)
# A failed download must not execute files left by an earlier attempt.
(root / "install.sh").write_text("stale installer fixture\n", encoding="utf-8")
(root / "install.sh.sshsig").write_text("stale signature fixture\n", encoding="utf-8")
trace = root / "trace.jsonl"
completed = subprocess.run(
["/bin/bash", "-c", commands],
cwd=root,
env={"PATH": f"{stubs}:/usr/bin:/bin", "TRACE": str(trace), **exits},
capture_output=True,
text=True,
timeout=5,
check=False,
)
if completed.stderr:
raise AssertionError(completed.stderr)
events = [json.loads(line) for line in trace.read_text().splitlines()]
return completed.returncode, events
class ReadmeEntrypointsTest(unittest.TestCase):
def test_signed_install_stops_at_each_failed_trust_step(self) -> None:
cases = (
({"CURL_1": "22"}, 22, ["curl"]),
({"CURL_2": "23"}, 23, ["curl", "curl"]),
({"VERIFY": "1"}, 1, ["curl", "curl", "ssh-keygen"]),
)
for relative_path in ENTRYPOINTS:
commands = installer_commands(read_repo_text(relative_path))
for exits, expected_exit, expected_steps in cases:
with self.subTest(page=relative_path, exits=exits):
code, events = exercise_install(commands, exits)
self.assertEqual([event["command"] for event in events], expected_steps)
self.assertEqual(code, expected_exit)
def test_verified_install_keeps_pinned_version_and_signer(self) -> None:
for relative_path in ENTRYPOINTS:
with self.subTest(page=relative_path):
commands = installer_commands(read_repo_text(relative_path))
self.assertIn(SIGNER, commands)
code, events = exercise_install(commands, {})
self.assertEqual(code, 0)
self.assertEqual(
[event["command"] for event in events],
["curl", "curl", "ssh-keygen", "bash"],
)
self.assertEqual(
[event["argv"][-1] for event in events[:2]],
[
"https://github.com/rcourtman/Pulse/releases/download/vX.Y.Z/install.sh",
"https://github.com/rcourtman/Pulse/releases/download/vX.Y.Z/install.sh.sshsig",
],
)
self.assertTrue(all(event["argv"][0] == "-fsSLO" for event in events[:2]))
self.assertEqual(events[2]["allowed_signers"], SIGNER + "\n")
verify = events[2]["argv"]
for option, value in (
("-I", "pulse-installer"),
("-n", "pulse-install"),
("-s", "install.sh.sshsig"),
):
self.assertEqual(verify[verify.index(option) + 1], value)
self.assertEqual(events[3]["argv"], ["install.sh", "--version", "vX.Y.Z"])
def test_installer_failure_remains_nonzero(self) -> None:
for relative_path in ENTRYPOINTS:
with self.subTest(page=relative_path):
code, events = exercise_install(
installer_commands(read_repo_text(relative_path)), {"INSTALL": "17"}
)
self.assertEqual(code, 17)
self.assertEqual(events[-1]["command"], "bash")
def test_entrypoint_plans_do_not_offer_retired_relay(self) -> None:
expected = {
"README.md": (
"31 March 2027", "Relay is no longer sold", "current price",
"for as long as their subscription continues", "own VPN or tunnel",
"ntfy, Gotify or Pushover", "not the web UI",
),
"docs/i18n/de/README.md": (
"31. März 2027", "Relay wird nicht mehr verkauft", "bisherigen Preis",
"solange ihr Abonnement weiterläuft", "eigenes VPN oder einen Tunnel",
"ntfy, Gotify oder Pushover", "nicht mit der Weboberfläche",
),
"docs/i18n/es/README.md": (
"31 de marzo de 2027", "Relay ya no se vende", "precio actual",
"mientras continúe su suscripción", "tu propia VPN o un túnel",
"ntfy, Gotify o Pushover", "no la interfaz web",
),
}
stale_claims = (
"**Relay** — Community plus", "**Pro** — Relay plus",
"Relay ergänzt sicheren Remote-Zugriff", "Relay añade acceso remoto seguro",
)
for relative_path, fragments in expected.items():
with self.subTest(page=relative_path):
prose = " ".join(read_repo_text(relative_path).split())
for fragment in fragments:
self.assertIn(fragment, prose)
for claim in stale_claims:
self.assertNotIn(claim, prose)
if __name__ == "__main__":
unittest.main()