mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-03 04:38:48 +00:00
Keep agent setup tokens out of commands and unverified downloads
Use existing Unix and Windows token-file installers, verified HTTPS downloads and inspection before execution across setup, retarget, profile and cleanup examples. Exercise private files, all profile arguments, rejected TLS and failed downloads with synthetic guest-local fixtures. Contract-Neutral: Documentation and executable documentation tests only; installer behaviour, privileges and runtime contracts are unchanged. Change-source: pulse-maintainer
This commit is contained in:
parent
36418af66b
commit
d5c187613a
6 changed files with 593 additions and 142 deletions
|
|
@ -9,13 +9,22 @@ If you are upgrading from older releases that used `pulse-sensor-proxy`, see the
|
|||
|
||||
## Recommended: Pulse Agent (Proxmox)
|
||||
|
||||
The unified agent runs on each Proxmox host and reports temperatures locally with no SSH keys needed.
|
||||
The unified agent runs on each Proxmox host and reports temperatures locally
|
||||
with no SSH keys needed. First complete the
|
||||
[private-file preparation](UNIFIED_AGENT.md#private-file-installation-linux-macos-and-nas)
|
||||
on that host: protect the Pulse agent token, download the installer from your
|
||||
Pulse server over verified HTTPS, and inspect it. Then use the same Pulse
|
||||
address and add the Proxmox collector flag:
|
||||
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token> --enable-proxmox
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-proxmox
|
||||
```
|
||||
|
||||
The Pulse agent token is not a Proxmox API token. Keep the secret out of
|
||||
command arguments and leave command execution disabled for monitoring.
|
||||
|
||||
Notes:
|
||||
- Install `lm-sensors` on each host (`apt install lm-sensors && sensors-detect --auto`).
|
||||
- Temperatures appear automatically once the agent reports.
|
||||
|
|
@ -104,11 +113,25 @@ ssh -i /path/to/key root@node "cat /sys/class/thermal/thermal_zone0/temp"
|
|||
|
||||
## Legacy Cleanup (If Upgrading)
|
||||
|
||||
If you still have the old sensor proxy installed from prior releases, remove it from each **Proxmox host** (not the Pulse container) with the supported cleanup helper:
|
||||
If you still have the old sensor proxy installed from prior releases, remove it
|
||||
from each **Proxmox host** (not the Pulse container) with the supported cleanup
|
||||
helper. In that host's administrative shell, download it to a private directory:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh | \
|
||||
sudo bash -s -- --uninstall --purge --local-only
|
||||
umask 077
|
||||
mkdir -p "$HOME/.config/pulse"
|
||||
chmod 700 "$HOME/.config/pulse"
|
||||
curl --fail --silent --show-error --connect-timeout 10 --max-time 60 \
|
||||
--output "$HOME/.config/pulse/sensor-proxy-uninstall.sh" \
|
||||
https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh
|
||||
```
|
||||
|
||||
Stop if the download fails and inspect the saved script before running it.
|
||||
Do not pipe a web response into a privileged shell:
|
||||
|
||||
```bash
|
||||
bash "$HOME/.config/pulse/sensor-proxy-uninstall.sh" \
|
||||
--uninstall --purge --local-only
|
||||
```
|
||||
|
||||
`--local-only` avoids cluster SSH entirely; run the command once on every
|
||||
|
|
@ -122,8 +145,8 @@ remote portion fail after local cleanup completes.
|
|||
If you also want to remove the old `pulse-monitor@pam` API user and tokens before re-adding the node, include `--remove-proxmox-access`:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh | \
|
||||
sudo bash -s -- --uninstall --purge --remove-proxmox-access --local-only
|
||||
bash "$HOME/.config/pulse/sensor-proxy-uninstall.sh" \
|
||||
--uninstall --purge --remove-proxmox-access --local-only
|
||||
```
|
||||
|
||||
Reinstalling or upgrading the Pulse container does **not** remove the sensor proxy from the host — they are separate installations. If you skip this cleanup, the selfheal timer will keep running and may generate recurring `TASK ERROR` entries in the Proxmox task log.
|
||||
|
|
|
|||
|
|
@ -27,18 +27,25 @@ preventing duplicate root-SSH sensor polling between cluster peers.
|
|||
|
||||
## Quick Start
|
||||
|
||||
Generate an installation command in the UI:
|
||||
Choose the host profile and create a monitoring token in the UI:
|
||||
**Settings → Infrastructure → Install on a host**
|
||||
|
||||
Choose a target profile in that screen when you want explicit install flags for Docker, Kubernetes, Proxmox VE, or Proxmox Backup Server.
|
||||
|
||||
The generated command is not tied to a single machine. For a Proxmox VE
|
||||
cluster, one API connection already provides cluster-wide inventory; the agent
|
||||
is per host, so run the same generated command on each cluster node where you
|
||||
want agent-provided telemetry (temperatures, SMART, host identity). Each agent
|
||||
registers itself and attaches to its own cluster member.
|
||||
Keep the token out of shell commands, history, URLs and screenshots. Use the
|
||||
private-file installation steps below; the command arguments contain only the
|
||||
file's path. A Pulse agent token is not a Proxmox or PBS API token. Monitoring
|
||||
does not require command execution: leave that option off unless you intend
|
||||
to grant it. See [Agent Security](AGENT_SECURITY.md) before choosing a root
|
||||
host agent instead of an API connection or the opt-in Linux safe profile.
|
||||
|
||||
The same generated command is also the supported v5-to-v6 agent upgrade path.
|
||||
For a Proxmox VE cluster, one API connection already provides cluster-wide
|
||||
inventory; the agent is per host. Repeat the private-file installation on each
|
||||
cluster node where you want agent-provided telemetry (temperatures, SMART, host
|
||||
identity), using the intended profile. Each agent registers itself and attaches
|
||||
to its own cluster member.
|
||||
|
||||
The same installer is also the supported v5-to-v6 agent upgrade path.
|
||||
Run it on the host that already has the v5 `pulse-agent` service to replace the
|
||||
binary and service configuration in place; do not uninstall the old service
|
||||
first unless you are intentionally removing that host from Pulse.
|
||||
|
|
@ -51,24 +58,29 @@ agents initiate the connection. Prefer a stable DNS name for the primary URL
|
|||
so replacing the Pulse host does not require an agent migration.
|
||||
|
||||
After importing the configuration on a Pulse server with a different address,
|
||||
retarget each existing standard Linux agent from that agent machine:
|
||||
retarget each existing standard Linux agent from that agent machine. Download
|
||||
and inspect the installer from the **new** Pulse address using the HTTPS
|
||||
preparation below; retargeting reuses the saved credential, so do not create
|
||||
another token just for this operation:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://pulse.example.com:7655/install.sh | \
|
||||
sudo bash -s -- --retarget --url https://pulse.example.com:7655
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--retarget --url https://pulse.example.com
|
||||
```
|
||||
|
||||
The retarget operation recovers the existing token, agent ID, enabled
|
||||
collectors, and other service options. It does not carry the old endpoint's
|
||||
TLS bypass, custom CA, or certificate fingerprint to the new address. Supply
|
||||
`--cacert`, `--server-fingerprint`, or (only on a trusted network)
|
||||
`--insecure` explicitly when the new endpoint requires it. The script must
|
||||
come from the new server so it supports the retarget operation. A newly
|
||||
generated full installation command from **Settings → Infrastructure → Install
|
||||
on a host** remains the fallback.
|
||||
`--cacert` or an independently verified `--server-fingerprint` explicitly
|
||||
when the new agent endpoint requires it. The installer download itself must
|
||||
use a trusted certificate or a separately verified CA file; an agent
|
||||
fingerprint option does not verify that earlier download. The script must
|
||||
come from the new server so it supports retargeting. If retargeting is not
|
||||
supported on that host, use the private-file installation below with the
|
||||
intended profile, rather than uninstalling the existing service first.
|
||||
|
||||
On Windows, run the full generated PowerShell installation command from the
|
||||
new Pulse server as Administrator, including the desired collector options.
|
||||
On Windows, use the PowerShell private-file installation below from the new
|
||||
Pulse server as Administrator, including the desired collector options.
|
||||
Do not expect the configuration import itself to make agent-only machines
|
||||
appear at the new address.
|
||||
|
||||
|
|
@ -83,42 +95,96 @@ the agent has reported, and confirm the host-local version with
|
|||
This is the agent installer served by your Pulse server. It is separate from the
|
||||
top-level GitHub `install.sh`, which installs or updates the Pulse server itself.
|
||||
|
||||
### Linux (systemd)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
### Private-file installation: Linux, macOS and NAS
|
||||
|
||||
### macOS
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
Use an administrative shell **on the host being monitored**, not the Pulse
|
||||
server or an unrelated container. Linux/systemd, macOS, Synology and TrueNAS
|
||||
use the same preparation. TrueNAS SCALE uses systemd; CORE uses rc.d. An API
|
||||
connection is usually enough for TrueNAS inventory and usage; an agent is
|
||||
optional for host-local data.
|
||||
|
||||
1. In that shell, protect the file before opening the editor. Save only the
|
||||
Pulse agent token, with no quotes or header. Use your own private directory
|
||||
and regular files, not shared paths or symlinks:
|
||||
|
||||
```bash
|
||||
umask 077
|
||||
mkdir -p "$HOME/.config/pulse"
|
||||
chmod 700 "$HOME/.config/pulse"
|
||||
touch "$HOME/.config/pulse/agent-token"
|
||||
chmod 600 "$HOME/.config/pulse/agent-token"
|
||||
vi "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
2. Replace `https://pulse.example.com` with your Pulse server's HTTPS address
|
||||
in both the download and installation commands. Download to a file:
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --connect-timeout 10 --max-time 60 \
|
||||
--output "$HOME/.config/pulse/agent-install.sh" \
|
||||
https://pulse.example.com/install.sh
|
||||
```
|
||||
|
||||
**Stop if the download fails.** Inspect the saved script before executing
|
||||
it. Do not bypass certificate verification or pipe an unchecked response
|
||||
into a privileged shell. For a private CA, add curl's `--cacert` with the
|
||||
separately verified CA file, and supply the installer's `--cacert` option
|
||||
for the agent connection as well. Do not substitute GitHub's top-level
|
||||
`install.sh`: that installs the Pulse server, not the agent.
|
||||
|
||||
3. Install with the private token file, adding a profile from
|
||||
[Installation Options](#installation-options) when needed:
|
||||
|
||||
```bash
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
Keep the bootstrap token file private and remove that copy when no longer
|
||||
needed; do not delete the installed agent's runtime credential. Check a fresh
|
||||
report in Pulse and `pulse-agent --version` on the host. A started service or
|
||||
hardware capacity alone does not prove that every requested metric is present.
|
||||
|
||||
### Windows (PowerShell, run as Administrator)
|
||||
|
||||
Use a **new** setup directory under your own Windows profile; if it already
|
||||
exists, choose another name. The following preparation grants access only to
|
||||
your account and SYSTEM. Stop if directory creation or the ACL command fails:
|
||||
|
||||
```powershell
|
||||
irm http://<pulse-ip>:7655/install.ps1 | iex
|
||||
$setupDir = Join-Path $env:USERPROFILE 'PulseAgentSetup'
|
||||
New-Item -ItemType Directory -Path $setupDir -ErrorAction Stop | Out-Null
|
||||
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||
icacls.exe $setupDir /inheritance:r /grant:r "*${userSid}:(OI)(CI)F" '*S-1-5-18:(OI)(CI)F'
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not protect the setup directory' }
|
||||
$tokenFile = Join-Path $setupDir 'agent-token.txt'
|
||||
New-Item -ItemType File -Path $tokenFile -ErrorAction Stop | Out-Null
|
||||
Start-Process -FilePath notepad.exe -ArgumentList "`"$tokenFile`"" -Wait
|
||||
```
|
||||
|
||||
With environment variables:
|
||||
In Notepad, save only the Pulse agent token, with no quotes or header. Never
|
||||
paste it into a PowerShell command or assign a literal secret to an environment
|
||||
variable. Replace the example HTTPS address in both commands below. Download
|
||||
and inspect the script first; do not use `Invoke-Expression` on a web response:
|
||||
|
||||
```powershell
|
||||
$env:PULSE_URL="http://<pulse-ip>:7655"
|
||||
$env:PULSE_TOKEN="<api-token>"
|
||||
irm http://<pulse-ip>:7655/install.ps1 | iex
|
||||
$installerFile = Join-Path $setupDir 'install.ps1'
|
||||
Invoke-WebRequest -Uri 'https://pulse.example.com/install.ps1' -OutFile $installerFile -ErrorAction Stop
|
||||
```
|
||||
|
||||
### Synology NAS
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
Only after a successful download and inspection, run the saved script. Clear
|
||||
an old `PULSE_TOKEN` environment value so it cannot override the file:
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:PULSE_TOKEN -ErrorAction SilentlyContinue
|
||||
& $installerFile -Url 'https://pulse.example.com' -TokenFile $tokenFile
|
||||
```
|
||||
|
||||
### TrueNAS SCALE/CORE
|
||||
TrueNAS SCALE and TrueNAS CORE are both supported. The installer auto-detects the platform and configures the appropriate service manager (systemd for SCALE, rc.d for CORE).
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
Keep the setup directory private and remove the bootstrap copy when no longer
|
||||
needed, not the installed service's token. Verify a fresh report and the
|
||||
installed agent version; the remaining platform trust trade-offs are in
|
||||
[Agent Security](AGENT_SECURITY.md).
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -550,46 +616,58 @@ operators are comfortable with Proxmox-side guest probing.
|
|||
|
||||
## Installation Options
|
||||
|
||||
These commands use the protected token file and successfully downloaded,
|
||||
inspected installer from [Private-file installation](#private-file-installation-linux-macos-and-nas).
|
||||
Use the same HTTPS address in preparation and installation. The flags below
|
||||
choose collectors; they do not grant command execution.
|
||||
|
||||
### Simple Install (host + Docker auto-detect)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token>
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
### Proxmox VE Node (explicit profile)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-proxmox --proxmox-type pve
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-proxmox --proxmox-type pve
|
||||
```
|
||||
|
||||
### Proxmox Backup Server Node (explicit profile)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-proxmox --proxmox-type pbs
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-proxmox --proxmox-type pbs
|
||||
```
|
||||
|
||||
### Force Enable Docker (if auto-detection fails)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-docker
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-docker
|
||||
```
|
||||
|
||||
### Disable Docker (even if detected)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-docker=false
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-docker=false
|
||||
```
|
||||
|
||||
### Host + Kubernetes Monitoring
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-kubernetes
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-kubernetes
|
||||
```
|
||||
|
||||
### Docker Monitoring Only
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-host=false --enable-docker
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-host=false --enable-docker
|
||||
```
|
||||
|
||||
### Exclude Specific Disks from Monitoring
|
||||
|
|
@ -693,9 +771,10 @@ installer path instead of relying on a plain-HTTP first hop.
|
|||
|
||||
To disable auto-updates:
|
||||
```bash
|
||||
# During installation
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --disable-auto-update
|
||||
# After private-file preparation and successful installer inspection
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --disable-auto-update
|
||||
|
||||
# Or set environment variable
|
||||
PULSE_DISABLE_AUTO_UPDATE=true
|
||||
|
|
@ -718,8 +797,11 @@ See [Centralized Agent Management](CENTRALIZED_MANAGEMENT.md) for supported keys
|
|||
|
||||
## Uninstall
|
||||
|
||||
Download and inspect the agent installer using the HTTPS preparation above
|
||||
before running this on the agent host. Uninstallation needs no new token:
|
||||
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | bash -s -- --uninstall
|
||||
bash "$HOME/.config/pulse/agent-install.sh" --uninstall
|
||||
```
|
||||
|
||||
This removes:
|
||||
|
|
@ -805,10 +887,13 @@ Unraid), `/tmp` and `/usr/local/bin` share that filesystem, so both the staged
|
|||
and installed copy must fit at once.
|
||||
|
||||
If the check fails because `/tmp` is on a constrained root, point `TMPDIR` at a
|
||||
directory on a data volume and re-run the installer:
|
||||
directory on a data volume and re-run the already downloaded and inspected
|
||||
agent installer from the private-file preparation above:
|
||||
|
||||
```bash
|
||||
TMPDIR=/share/CACHEDEV1_DATA/tmp bash install.sh --url http://pulse --token <token>
|
||||
TMPDIR=/share/CACHEDEV1_DATA/tmp bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
(`mktemp` honours `TMPDIR`, so this moves the staging copy off the RAM root.
|
||||
|
|
|
|||
|
|
@ -9,13 +9,22 @@ If you are upgrading from older releases that used `pulse-sensor-proxy`, see the
|
|||
|
||||
## Recommended: Pulse Agent (Proxmox)
|
||||
|
||||
The unified agent runs on each Proxmox host and reports temperatures locally with no SSH keys needed.
|
||||
The unified agent runs on each Proxmox host and reports temperatures locally
|
||||
with no SSH keys needed. First complete the
|
||||
[private-file preparation](UNIFIED_AGENT.md#private-file-installation-linux-macos-and-nas)
|
||||
on that host: protect the Pulse agent token, download the installer from your
|
||||
Pulse server over verified HTTPS, and inspect it. Then use the same Pulse
|
||||
address and add the Proxmox collector flag:
|
||||
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token> --enable-proxmox
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-proxmox
|
||||
```
|
||||
|
||||
The Pulse agent token is not a Proxmox API token. Keep the secret out of
|
||||
command arguments and leave command execution disabled for monitoring.
|
||||
|
||||
Notes:
|
||||
- Install `lm-sensors` on each host (`apt install lm-sensors && sensors-detect --auto`).
|
||||
- Temperatures appear automatically once the agent reports.
|
||||
|
|
@ -104,11 +113,25 @@ ssh -i /path/to/key root@node "cat /sys/class/thermal/thermal_zone0/temp"
|
|||
|
||||
## Legacy Cleanup (If Upgrading)
|
||||
|
||||
If you still have the old sensor proxy installed from prior releases, remove it from each **Proxmox host** (not the Pulse container) with the supported cleanup helper:
|
||||
If you still have the old sensor proxy installed from prior releases, remove it
|
||||
from each **Proxmox host** (not the Pulse container) with the supported cleanup
|
||||
helper. In that host's administrative shell, download it to a private directory:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh | \
|
||||
sudo bash -s -- --uninstall --purge --local-only
|
||||
umask 077
|
||||
mkdir -p "$HOME/.config/pulse"
|
||||
chmod 700 "$HOME/.config/pulse"
|
||||
curl --fail --silent --show-error --connect-timeout 10 --max-time 60 \
|
||||
--output "$HOME/.config/pulse/sensor-proxy-uninstall.sh" \
|
||||
https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh
|
||||
```
|
||||
|
||||
Stop if the download fails and inspect the saved script before running it.
|
||||
Do not pipe a web response into a privileged shell:
|
||||
|
||||
```bash
|
||||
bash "$HOME/.config/pulse/sensor-proxy-uninstall.sh" \
|
||||
--uninstall --purge --local-only
|
||||
```
|
||||
|
||||
`--local-only` avoids cluster SSH entirely; run the command once on every
|
||||
|
|
@ -122,8 +145,8 @@ remote portion fail after local cleanup completes.
|
|||
If you also want to remove the old `pulse-monitor@pam` API user and tokens before re-adding the node, include `--remove-proxmox-access`:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh | \
|
||||
sudo bash -s -- --uninstall --purge --remove-proxmox-access --local-only
|
||||
bash "$HOME/.config/pulse/sensor-proxy-uninstall.sh" \
|
||||
--uninstall --purge --remove-proxmox-access --local-only
|
||||
```
|
||||
|
||||
Reinstalling or upgrading the Pulse container does **not** remove the sensor proxy from the host — they are separate installations. If you skip this cleanup, the selfheal timer will keep running and may generate recurring `TASK ERROR` entries in the Proxmox task log.
|
||||
|
|
|
|||
|
|
@ -27,18 +27,25 @@ preventing duplicate root-SSH sensor polling between cluster peers.
|
|||
|
||||
## Quick Start
|
||||
|
||||
Generate an installation command in the UI:
|
||||
Choose the host profile and create a monitoring token in the UI:
|
||||
**Settings → Infrastructure → Install on a host**
|
||||
|
||||
Choose a target profile in that screen when you want explicit install flags for Docker, Kubernetes, Proxmox VE, or Proxmox Backup Server.
|
||||
|
||||
The generated command is not tied to a single machine. For a Proxmox VE
|
||||
cluster, one API connection already provides cluster-wide inventory; the agent
|
||||
is per host, so run the same generated command on each cluster node where you
|
||||
want agent-provided telemetry (temperatures, SMART, host identity). Each agent
|
||||
registers itself and attaches to its own cluster member.
|
||||
Keep the token out of shell commands, history, URLs and screenshots. Use the
|
||||
private-file installation steps below; the command arguments contain only the
|
||||
file's path. A Pulse agent token is not a Proxmox or PBS API token. Monitoring
|
||||
does not require command execution: leave that option off unless you intend
|
||||
to grant it. See [Agent Security](AGENT_SECURITY.md) before choosing a root
|
||||
host agent instead of an API connection or the opt-in Linux safe profile.
|
||||
|
||||
The same generated command is also the supported v5-to-v6 agent upgrade path.
|
||||
For a Proxmox VE cluster, one API connection already provides cluster-wide
|
||||
inventory; the agent is per host. Repeat the private-file installation on each
|
||||
cluster node where you want agent-provided telemetry (temperatures, SMART, host
|
||||
identity), using the intended profile. Each agent registers itself and attaches
|
||||
to its own cluster member.
|
||||
|
||||
The same installer is also the supported v5-to-v6 agent upgrade path.
|
||||
Run it on the host that already has the v5 `pulse-agent` service to replace the
|
||||
binary and service configuration in place; do not uninstall the old service
|
||||
first unless you are intentionally removing that host from Pulse.
|
||||
|
|
@ -51,24 +58,29 @@ agents initiate the connection. Prefer a stable DNS name for the primary URL
|
|||
so replacing the Pulse host does not require an agent migration.
|
||||
|
||||
After importing the configuration on a Pulse server with a different address,
|
||||
retarget each existing standard Linux agent from that agent machine:
|
||||
retarget each existing standard Linux agent from that agent machine. Download
|
||||
and inspect the installer from the **new** Pulse address using the HTTPS
|
||||
preparation below; retargeting reuses the saved credential, so do not create
|
||||
another token just for this operation:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://pulse.example.com:7655/install.sh | \
|
||||
sudo bash -s -- --retarget --url https://pulse.example.com:7655
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--retarget --url https://pulse.example.com
|
||||
```
|
||||
|
||||
The retarget operation recovers the existing token, agent ID, enabled
|
||||
collectors, and other service options. It does not carry the old endpoint's
|
||||
TLS bypass, custom CA, or certificate fingerprint to the new address. Supply
|
||||
`--cacert`, `--server-fingerprint`, or (only on a trusted network)
|
||||
`--insecure` explicitly when the new endpoint requires it. The script must
|
||||
come from the new server so it supports the retarget operation. A newly
|
||||
generated full installation command from **Settings → Infrastructure → Install
|
||||
on a host** remains the fallback.
|
||||
`--cacert` or an independently verified `--server-fingerprint` explicitly
|
||||
when the new agent endpoint requires it. The installer download itself must
|
||||
use a trusted certificate or a separately verified CA file; an agent
|
||||
fingerprint option does not verify that earlier download. The script must
|
||||
come from the new server so it supports retargeting. If retargeting is not
|
||||
supported on that host, use the private-file installation below with the
|
||||
intended profile, rather than uninstalling the existing service first.
|
||||
|
||||
On Windows, run the full generated PowerShell installation command from the
|
||||
new Pulse server as Administrator, including the desired collector options.
|
||||
On Windows, use the PowerShell private-file installation below from the new
|
||||
Pulse server as Administrator, including the desired collector options.
|
||||
Do not expect the configuration import itself to make agent-only machines
|
||||
appear at the new address.
|
||||
|
||||
|
|
@ -83,42 +95,96 @@ the agent has reported, and confirm the host-local version with
|
|||
This is the agent installer served by your Pulse server. It is separate from the
|
||||
top-level GitHub `install.sh`, which installs or updates the Pulse server itself.
|
||||
|
||||
### Linux (systemd)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
### Private-file installation: Linux, macOS and NAS
|
||||
|
||||
### macOS
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
Use an administrative shell **on the host being monitored**, not the Pulse
|
||||
server or an unrelated container. Linux/systemd, macOS, Synology and TrueNAS
|
||||
use the same preparation. TrueNAS SCALE uses systemd; CORE uses rc.d. An API
|
||||
connection is usually enough for TrueNAS inventory and usage; an agent is
|
||||
optional for host-local data.
|
||||
|
||||
1. In that shell, protect the file before opening the editor. Save only the
|
||||
Pulse agent token, with no quotes or header. Use your own private directory
|
||||
and regular files, not shared paths or symlinks:
|
||||
|
||||
```bash
|
||||
umask 077
|
||||
mkdir -p "$HOME/.config/pulse"
|
||||
chmod 700 "$HOME/.config/pulse"
|
||||
touch "$HOME/.config/pulse/agent-token"
|
||||
chmod 600 "$HOME/.config/pulse/agent-token"
|
||||
vi "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
2. Replace `https://pulse.example.com` with your Pulse server's HTTPS address
|
||||
in both the download and installation commands. Download to a file:
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --connect-timeout 10 --max-time 60 \
|
||||
--output "$HOME/.config/pulse/agent-install.sh" \
|
||||
https://pulse.example.com/install.sh
|
||||
```
|
||||
|
||||
**Stop if the download fails.** Inspect the saved script before executing
|
||||
it. Do not bypass certificate verification or pipe an unchecked response
|
||||
into a privileged shell. For a private CA, add curl's `--cacert` with the
|
||||
separately verified CA file, and supply the installer's `--cacert` option
|
||||
for the agent connection as well. Do not substitute GitHub's top-level
|
||||
`install.sh`: that installs the Pulse server, not the agent.
|
||||
|
||||
3. Install with the private token file, adding a profile from
|
||||
[Installation Options](#installation-options) when needed:
|
||||
|
||||
```bash
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
Keep the bootstrap token file private and remove that copy when no longer
|
||||
needed; do not delete the installed agent's runtime credential. Check a fresh
|
||||
report in Pulse and `pulse-agent --version` on the host. A started service or
|
||||
hardware capacity alone does not prove that every requested metric is present.
|
||||
|
||||
### Windows (PowerShell, run as Administrator)
|
||||
|
||||
Use a **new** setup directory under your own Windows profile; if it already
|
||||
exists, choose another name. The following preparation grants access only to
|
||||
your account and SYSTEM. Stop if directory creation or the ACL command fails:
|
||||
|
||||
```powershell
|
||||
irm http://<pulse-ip>:7655/install.ps1 | iex
|
||||
$setupDir = Join-Path $env:USERPROFILE 'PulseAgentSetup'
|
||||
New-Item -ItemType Directory -Path $setupDir -ErrorAction Stop | Out-Null
|
||||
$userSid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||
icacls.exe $setupDir /inheritance:r /grant:r "*${userSid}:(OI)(CI)F" '*S-1-5-18:(OI)(CI)F'
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Could not protect the setup directory' }
|
||||
$tokenFile = Join-Path $setupDir 'agent-token.txt'
|
||||
New-Item -ItemType File -Path $tokenFile -ErrorAction Stop | Out-Null
|
||||
Start-Process -FilePath notepad.exe -ArgumentList "`"$tokenFile`"" -Wait
|
||||
```
|
||||
|
||||
With environment variables:
|
||||
In Notepad, save only the Pulse agent token, with no quotes or header. Never
|
||||
paste it into a PowerShell command or assign a literal secret to an environment
|
||||
variable. Replace the example HTTPS address in both commands below. Download
|
||||
and inspect the script first; do not use `Invoke-Expression` on a web response:
|
||||
|
||||
```powershell
|
||||
$env:PULSE_URL="http://<pulse-ip>:7655"
|
||||
$env:PULSE_TOKEN="<api-token>"
|
||||
irm http://<pulse-ip>:7655/install.ps1 | iex
|
||||
$installerFile = Join-Path $setupDir 'install.ps1'
|
||||
Invoke-WebRequest -Uri 'https://pulse.example.com/install.ps1' -OutFile $installerFile -ErrorAction Stop
|
||||
```
|
||||
|
||||
### Synology NAS
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
Only after a successful download and inspection, run the saved script. Clear
|
||||
an old `PULSE_TOKEN` environment value so it cannot override the file:
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:PULSE_TOKEN -ErrorAction SilentlyContinue
|
||||
& $installerFile -Url 'https://pulse.example.com' -TokenFile $tokenFile
|
||||
```
|
||||
|
||||
### TrueNAS SCALE/CORE
|
||||
TrueNAS SCALE and TrueNAS CORE are both supported. The installer auto-detects the platform and configures the appropriate service manager (systemd for SCALE, rc.d for CORE).
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <api-token>
|
||||
```
|
||||
Keep the setup directory private and remove the bootstrap copy when no longer
|
||||
needed, not the installed service's token. Verify a fresh report and the
|
||||
installed agent version; the remaining platform trust trade-offs are in
|
||||
[Agent Security](AGENT_SECURITY.md).
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -550,46 +616,58 @@ operators are comfortable with Proxmox-side guest probing.
|
|||
|
||||
## Installation Options
|
||||
|
||||
These commands use the protected token file and successfully downloaded,
|
||||
inspected installer from [Private-file installation](#private-file-installation-linux-macos-and-nas).
|
||||
Use the same HTTPS address in preparation and installation. The flags below
|
||||
choose collectors; they do not grant command execution.
|
||||
|
||||
### Simple Install (host + Docker auto-detect)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token>
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
### Proxmox VE Node (explicit profile)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-proxmox --proxmox-type pve
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-proxmox --proxmox-type pve
|
||||
```
|
||||
|
||||
### Proxmox Backup Server Node (explicit profile)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-proxmox --proxmox-type pbs
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-proxmox --proxmox-type pbs
|
||||
```
|
||||
|
||||
### Force Enable Docker (if auto-detection fails)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-docker
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-docker
|
||||
```
|
||||
|
||||
### Disable Docker (even if detected)
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-docker=false
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-docker=false
|
||||
```
|
||||
|
||||
### Host + Kubernetes Monitoring
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-kubernetes
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-kubernetes
|
||||
```
|
||||
|
||||
### Docker Monitoring Only
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --enable-host=false --enable-docker
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --enable-host=false --enable-docker
|
||||
```
|
||||
|
||||
### Exclude Specific Disks from Monitoring
|
||||
|
|
@ -693,9 +771,10 @@ installer path instead of relying on a plain-HTTP first hop.
|
|||
|
||||
To disable auto-updates:
|
||||
```bash
|
||||
# During installation
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | \
|
||||
bash -s -- --url http://<pulse-ip>:7655 --token <token> --disable-auto-update
|
||||
# After private-file preparation and successful installer inspection
|
||||
bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token" --disable-auto-update
|
||||
|
||||
# Or set environment variable
|
||||
PULSE_DISABLE_AUTO_UPDATE=true
|
||||
|
|
@ -718,8 +797,11 @@ See [Centralized Agent Management](CENTRALIZED_MANAGEMENT.md) for supported keys
|
|||
|
||||
## Uninstall
|
||||
|
||||
Download and inspect the agent installer using the HTTPS preparation above
|
||||
before running this on the agent host. Uninstallation needs no new token:
|
||||
|
||||
```bash
|
||||
curl -fsSL http://<pulse-ip>:7655/install.sh | bash -s -- --uninstall
|
||||
bash "$HOME/.config/pulse/agent-install.sh" --uninstall
|
||||
```
|
||||
|
||||
This removes:
|
||||
|
|
@ -805,10 +887,13 @@ Unraid), `/tmp` and `/usr/local/bin` share that filesystem, so both the staged
|
|||
and installed copy must fit at once.
|
||||
|
||||
If the check fails because `/tmp` is on a constrained root, point `TMPDIR` at a
|
||||
directory on a data volume and re-run the installer:
|
||||
directory on a data volume and re-run the already downloaded and inspected
|
||||
agent installer from the private-file preparation above:
|
||||
|
||||
```bash
|
||||
TMPDIR=/share/CACHEDEV1_DATA/tmp bash install.sh --url http://pulse --token <token>
|
||||
TMPDIR=/share/CACHEDEV1_DATA/tmp bash "$HOME/.config/pulse/agent-install.sh" \
|
||||
--url https://pulse.example.com \
|
||||
--token-file "$HOME/.config/pulse/agent-token"
|
||||
```
|
||||
|
||||
(`mktemp` honours `TMPDIR`, so this moves the staging copy off the RAM root.
|
||||
|
|
|
|||
235
scripts/tests/test_agent_docs.py
Normal file
235
scripts/tests/test_agent_docs.py
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Exercise documented agent setup without installing a service or real token.
|
||||
|
||||
TLS listeners are guest-local: run with pulse-worker-source-proof. Windows
|
||||
checks cover the documented source contract, not native ACL/service execution.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_pbs_docs import certificate, fixture_environment, server
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
NAMES = ("UNIFIED_AGENT.md", "TEMPERATURE_MONITORING.md")
|
||||
TOKEN = "synthetic-agent-docs-secret"
|
||||
CLEANUP_PATH = "/rcourtman/Pulse/main/scripts/uninstall-sensor-proxy.sh"
|
||||
|
||||
|
||||
def blocks(name, language="bash"):
|
||||
text = re.sub(r"^ {1,3}", "", (ROOT / "docs" / name).read_text(), flags=re.MULTILINE)
|
||||
return re.findall(r"```" + language + r"\n(.*?)```", text, re.DOTALL)
|
||||
|
||||
|
||||
def recipe(name, needle):
|
||||
found = [block for block in blocks(name) if needle in block]
|
||||
if len(found) != 1:
|
||||
raise AssertionError(f"expected one {name} recipe containing {needle!r}")
|
||||
return found[0]
|
||||
|
||||
|
||||
class AgentDocsTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.temporary = tempfile.TemporaryDirectory()
|
||||
cls.cert, cls.key = certificate(Path(cls.temporary.name), "agent-docs")
|
||||
cls.other_cert, _ = certificate(Path(cls.temporary.name), "untrusted")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.temporary.cleanup()
|
||||
|
||||
def test_mirrors_and_no_secret_or_unchecked_bootstrap_commands(self):
|
||||
for name in NAMES:
|
||||
with self.subTest(name=name):
|
||||
doc = (ROOT / "docs" / name).read_bytes()
|
||||
self.assertEqual(doc, (ROOT / "frontend-modern/public/docs" / name).read_bytes())
|
||||
shell = "\n".join(blocks(name))
|
||||
self.assertNotRegex(shell, r"--token(?:\s|=)|PULSE_TOKEN=|curl[^\n]*http://")
|
||||
self.assertNotRegex(shell, r"curl[^`]*\|\s*(?:sudo\s+)?bash")
|
||||
self.assertIn("Stop if the download fails", doc.decode())
|
||||
windows = "\n".join(blocks(NAMES[0], "powershell"))
|
||||
self.assertNotRegex(windows, r"(?i)\biex\b|\birm\b|Invoke-Expression|http://|\$env:PULSE_TOKEN\s*=")
|
||||
|
||||
def test_changed_unix_recipes_parse(self):
|
||||
count = 0
|
||||
for name in NAMES:
|
||||
for block in blocks(name):
|
||||
if any(word in block for word in ("agent-install.sh", "agent-token", "sensor-proxy-uninstall.sh")):
|
||||
with self.subTest(name=name, command=block):
|
||||
subprocess.run(["bash", "-n", "-c", block], check=True, capture_output=True)
|
||||
count += 1
|
||||
self.assertEqual(count, 18)
|
||||
|
||||
def test_private_file_preparation_preserves_existing_token(self):
|
||||
command = recipe(NAMES[0], 'vi "$HOME/.config/pulse/agent-token"')
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
home = Path(temporary)
|
||||
tools = home / "tools"
|
||||
tools.mkdir()
|
||||
editor = tools / "vi"
|
||||
editor.write_text('#!/bin/sh\n[ "$#" = 1 ] && [ -f "$1" ]\n')
|
||||
editor.chmod(0o700)
|
||||
env = fixture_environment(home)
|
||||
env["PATH"] = f"{tools}:{os.environ['PATH']}"
|
||||
token = home / ".config/pulse/agent-token"
|
||||
for existing in (False, True):
|
||||
if existing:
|
||||
token.write_text(TOKEN + "\n")
|
||||
token.chmod(0o644)
|
||||
token.parent.chmod(0o755)
|
||||
result = subprocess.run(["bash", "-eu", "-c", command], env=env, capture_output=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||
self.assertEqual(stat.S_IMODE(token.stat().st_mode), 0o600)
|
||||
self.assertEqual(stat.S_IMODE(token.parent.stat().st_mode), 0o700)
|
||||
self.assertEqual(token.read_text(), TOKEN + "\n" if existing else "")
|
||||
self.assertNotIn(TOKEN.encode(), result.stdout + result.stderr)
|
||||
|
||||
def test_windows_file_handoff_matches_installer_contract(self):
|
||||
windows = "\n".join(blocks(NAMES[0], "powershell"))
|
||||
for expected in ("-ErrorAction Stop", "/inheritance:r", "*${userSid}:(OI)(CI)F",
|
||||
"*S-1-5-18:(OI)(CI)F", "$LASTEXITCODE -ne 0",
|
||||
"-FilePath notepad.exe", "-Wait", "-OutFile $installerFile",
|
||||
"Remove-Item Env:PULSE_TOKEN", "-TokenFile $tokenFile"):
|
||||
self.assertIn(expected, windows)
|
||||
source = (ROOT / "scripts/install.ps1").read_text()
|
||||
self.assertIn('[string]$TokenFile = $env:PULSE_TOKEN_FILE,', source)
|
||||
self.assertIn('if ([string]::IsNullOrWhiteSpace($Token) -and -not [string]::IsNullOrWhiteSpace($TokenFile))', source)
|
||||
self.assertIn('(Get-Content -Path $resolvedTokenFile -Raw -ErrorAction Stop).Trim()', source)
|
||||
|
||||
def download(self, home, command, port, ca=None, hostname="localhost"):
|
||||
private = home / ".config/pulse"
|
||||
private.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
tools = home / "tools"
|
||||
tools.mkdir(exist_ok=True)
|
||||
recorder = tools / "curl"
|
||||
recorder.write_text(
|
||||
"#!/usr/bin/env python3\nimport json, os, sys\nfrom pathlib import Path\n"
|
||||
"Path(os.environ['CURL_RECEIPT']).write_text(json.dumps(sys.argv[1:]))\n"
|
||||
"os.execv(os.environ['REAL_CURL'], [os.environ['REAL_CURL'], *sys.argv[1:]])\n"
|
||||
)
|
||||
recorder.chmod(0o700)
|
||||
env = fixture_environment(home)
|
||||
env.update(PATH=f"{tools}:{os.environ['PATH']}", REAL_CURL=shutil.which("curl"),
|
||||
CURL_RECEIPT=str(home / "curl-argv.json"))
|
||||
command = command.replace("https://pulse.example.com", f"https://{hostname}:{port}")
|
||||
command = command.replace("https://raw.githubusercontent.com", f"https://{hostname}:{port}")
|
||||
if ca is not None:
|
||||
command = command.replace("curl ", f'curl --cacert "{ca}" ', 1)
|
||||
result = subprocess.run(["bash", "-eu", "-c", command], env=env, capture_output=True, timeout=20)
|
||||
argv = json.loads((home / "curl-argv.json").read_text())
|
||||
self.assertNotIn(TOKEN, " ".join(argv))
|
||||
self.assertNotIn(TOKEN.encode(), result.stdout + result.stderr)
|
||||
for argument in ("--connect-timeout", "--max-time", "--fail", "--output"):
|
||||
self.assertIn(argument, argv)
|
||||
self.assertNotIn("--insecure", argv)
|
||||
self.assertNotIn("-k", argv)
|
||||
return result
|
||||
|
||||
def test_downloads_require_verified_tls_and_success(self):
|
||||
for name, path, needle, output in (
|
||||
(NAMES[0], "/install.sh", '--output "$HOME/.config/pulse/agent-install.sh"', "agent-install.sh"),
|
||||
(NAMES[1], CLEANUP_PATH, '--output "$HOME/.config/pulse/sensor-proxy-uninstall.sh"', "sensor-proxy-uninstall.sh"),
|
||||
):
|
||||
for status, ca, hostname, expected in (
|
||||
(200, self.cert, "localhost", 0),
|
||||
(403, self.cert, "localhost", 22),
|
||||
(200, None, "localhost", 60),
|
||||
(200, self.other_cert, "localhost", 60),
|
||||
(200, self.cert, "127.0.0.1", 60),
|
||||
):
|
||||
with self.subTest(name=name, status=status, expected=expected):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
with server(self.cert, self.key, status, b"# harmless installer fixture\n", path) as (port, requests):
|
||||
home = Path(temporary)
|
||||
result = self.download(home, recipe(name, needle), port, ca, hostname)
|
||||
self.assertEqual(result.returncode, expected, result.stderr.decode())
|
||||
if expected == 0:
|
||||
self.assertEqual((home / ".config/pulse" / output).read_bytes(), b"# harmless installer fixture\n")
|
||||
if expected == 60:
|
||||
self.assertEqual(requests, [])
|
||||
for requested_path, headers in requests:
|
||||
self.assertEqual(requested_path, path)
|
||||
self.assertNotIn("Authorization", headers)
|
||||
|
||||
def test_every_profile_passes_only_private_file_not_token_arguments(self):
|
||||
profiles = []
|
||||
for name in NAMES:
|
||||
profiles.extend(block for block in blocks(name) if 'bash "$HOME/.config/pulse/agent-install.sh"' in block and "--token-file" in block)
|
||||
self.assertEqual(len(profiles), 11)
|
||||
fixture = '''#!/bin/bash
|
||||
python3 - "$@" <<'PY'
|
||||
import json, os, stat, sys
|
||||
from pathlib import Path
|
||||
args = sys.argv[1:]
|
||||
token = Path(args[args.index('--token-file') + 1])
|
||||
assert args.count('--token-file') == 1
|
||||
assert token.read_text().strip() == os.environ['EXPECTED_TOKEN']
|
||||
assert stat.S_IMODE(token.stat().st_mode) == 0o600
|
||||
assert os.environ['EXPECTED_TOKEN'] not in ' '.join(args)
|
||||
assert '--token' not in args
|
||||
Path(os.environ['INSTALL_RECEIPT']).write_text(json.dumps(args))
|
||||
PY
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
home = Path(temporary)
|
||||
private = home / ".config/pulse"
|
||||
private.mkdir(parents=True, mode=0o700)
|
||||
(private / "agent-install.sh").write_text(fixture)
|
||||
token = private / "agent-token"
|
||||
token.write_text(TOKEN + "\n")
|
||||
token.chmod(0o600)
|
||||
env = fixture_environment(home)
|
||||
env.update(EXPECTED_TOKEN=TOKEN, INSTALL_RECEIPT=str(home / "installer-argv.json"))
|
||||
for command in profiles:
|
||||
with self.subTest(command=command):
|
||||
result = subprocess.run(["bash", "-eu", "-c", command], env=env, capture_output=True, timeout=10)
|
||||
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||
args = json.loads((home / "installer-argv.json").read_text())
|
||||
self.assertEqual(args[:4], ["--url", "https://pulse.example.com", "--token-file", str(token)])
|
||||
self.assertNotIn(TOKEN.encode(), result.stdout + result.stderr)
|
||||
expected_profiles = [[], ["--enable-proxmox", "--proxmox-type", "pve"],
|
||||
["--enable-proxmox", "--proxmox-type", "pbs"],
|
||||
["--enable-docker"], ["--enable-docker=false"],
|
||||
["--enable-kubernetes"], ["--enable-host=false", "--enable-docker"],
|
||||
["--disable-auto-update"], ["--enable-proxmox"]]
|
||||
actual_profiles = [re.findall(r"--(?:enable-[a-z]+(?:=false)?|disable-auto-update|proxmox-type\s+(?:pve|pbs))", p) for p in profiles]
|
||||
for expected in expected_profiles:
|
||||
# Compare the actual argv tokens, retaining the type's value.
|
||||
self.assertIn(" ".join(expected), [" ".join(a) for a in actual_profiles])
|
||||
source = (ROOT / "scripts/install.sh").read_text()
|
||||
self.assertIn('--token-file) TOKEN_FILE_PATH="$2"; shift 2 ;;', source)
|
||||
self.assertIn('read_collector_token_file_safely "$TOKEN_FILE_PATH" true', source)
|
||||
|
||||
def test_retarget_uninstall_and_cleanup_use_saved_script_without_new_token(self):
|
||||
expected = (["--retarget", "--url", "https://pulse.example.com"], ["--uninstall"],
|
||||
["--uninstall", "--purge", "--local-only"],
|
||||
["--uninstall", "--purge", "--remove-proxmox-access", "--local-only"])
|
||||
commands = [recipe(NAMES[0], "--retarget --url"), recipe(NAMES[0], 'agent-install.sh" --uninstall'),
|
||||
recipe(NAMES[1], "--uninstall --purge --local-only"),
|
||||
recipe(NAMES[1], "--uninstall --purge --remove-proxmox-access")]
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
home = Path(temporary)
|
||||
private = home / ".config/pulse"
|
||||
private.mkdir(parents=True, mode=0o700)
|
||||
fixture = '#!/bin/bash\npython3 - "$@" <<\'PY\'\nimport json, os, sys\nfrom pathlib import Path\nPath(os.environ["INSTALL_RECEIPT"]).write_text(json.dumps(sys.argv[1:]))\nPY\n'
|
||||
for name in ("agent-install.sh", "sensor-proxy-uninstall.sh"):
|
||||
(private / name).write_text(fixture)
|
||||
env = fixture_environment(home)
|
||||
env["INSTALL_RECEIPT"] = str(home / "argv.json")
|
||||
for command, args in zip(commands, expected):
|
||||
result = subprocess.run(["bash", "-eu", "-c", command], env=env, capture_output=True, timeout=10)
|
||||
self.assertEqual(result.returncode, 0, result.stderr.decode())
|
||||
self.assertEqual(json.loads((home / "argv.json").read_text()), args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -63,7 +63,7 @@ def certificate(directory, name):
|
|||
|
||||
|
||||
@contextmanager
|
||||
def server(cert, key, status=200, installer=None):
|
||||
def server(cert, key, status=200, installer=None, installer_path="/install.sh"):
|
||||
requests = []
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
|
|
@ -71,7 +71,7 @@ def server(cert, key, status=200, installer=None):
|
|||
requests.append((self.path, dict(self.headers)))
|
||||
self.send_response(status)
|
||||
self.end_headers()
|
||||
body = installer if self.path == "/install.sh" else b'{"data":[]}\n'
|
||||
body = installer if self.path == installer_path else b'{"data":[]}\n'
|
||||
self.wfile.write(body or b"fixture error\n")
|
||||
|
||||
def log_message(self, *_args):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue