Table of Contents
- Agent Setup Guide
- Quick install
- 1. Issue an enrollment token
- 2. Block A — install and enroll
- 3. Run the init wizard
- 4. Block B — start and verify
- 5. Watch the dashboard turn green
- Detail: when something breaks
- 1. Generate an enrollment token
- 2. Install pipx
- 3. Install the agent
- 4. Enroll
- 5. Init
- 6. Git remote (optional)
- 7. Start
- 8. Container log shipping (optional)
- Host service logs
- Verify end-to-end
- Manual enrollment (rare)
- Troubleshooting
- Update
Agent Setup Guide
Install the Storm Pulse agent on a fresh VPS. The agent itself runs
as your unprivileged admin user — no stormpulse system user, no
root daemon, no sudo at runtime. A few one-time setup steps still
need sudo (apt install, creating a project directory under /opt);
those are clearly marked. Step 1 runs on the dashboard server; every
other step runs on the VPS.
Quick install
The happy path. Fill in 4 variables, paste two blocks, answer six wizard prompts in between.
1. Issue an enrollment token
Open the dashboard → Servers → your host → Issue enrollment token. The side panel gives you two things to copy:
- Enroll command — the complete rootless command, with your agent-id and one-time enrollment token pre-substituted. Paste it verbatim in Block A.
- Pulse token — long-lived. Paste it into the init wizard in phase 3.
Copy both before dismissing the panel — the warning "shown only once" is real.
2. Block A — install and enroll
On the VPS, paste this to install pipx and the agent:
sudo apt update && sudo apt install -y pipx
pipx ensurepath
export PATH="$HOME/.local/bin:$PATH"
pipx install git+https://git.stormdevelopments.ca/official-public/storm-pulse.git
Then paste the Enroll command you copied from the dashboard in step 1. It already contains your agent-id and enrollment token — nothing to substitute.
Here's an example of what it will look like:
stormpulse enroll \
--creds-dir ~/.config/stormpulse \
https://stormdevelopments.ca/api/enroll/ \
garage-alpha \
d650d061-3daf-45c8-a066-a3b91696f5cf
3. Run the init wizard
cd /opt/your-project # wherever your project lives (e.g. /opt/garage)
stormpulse init --user
Six prompts. Most auto-detect — accept defaults unless wrong:
- Pulse token — paste the value from step 1.
- Dashboard WebSocket URL — auto-derived, confirm.
- Project directory — defaults to the current dir.
- Compose file — auto-detected.
- Service name — parsed from the compose file.
- .env file — auto-detected, or skip.
4. Block B — start and verify
systemctl --user daemon-reload
systemctl --user enable --now stormpulse
journalctl --user -u stormpulse -f
Expect "Connected to dashboard" and periodic heartbeat/metrics sends in the log tail.
5. Watch the dashboard turn green
The server's status dot flips to green within seconds. CPU, memory, and disk metrics start streaming. You're done.
Detail: when something breaks
Same flow as the TL;DR, broken apart with explanation and failure modes per step.
1. Generate an enrollment token
Dashboard UI (preferred). Servers → your host → Issue enrollment token. Copy the Pulse token and Enrollment token from the side panel. The hostname becomes the agent-id and must match the Server record exactly.
CLI fallback. If you don't have dashboard access, run on the dashboard server:
HOST_NAME="your-host-name"
# bare-metal install:
python manage.py create_enrollment_token "$HOST_NAME"
# docker install:
docker compose --env-file .env -f docker/docker-compose.prod.yml \
exec web python manage.py create_enrollment_token "$HOST_NAME"
2. Install pipx
sudo apt update && sudo apt install -y pipx
pipx ensurepath
export PATH="$HOME/.local/bin:$PATH"
ensurepath edits your shell rc file for future shells; export
fixes the current shell. You need both — pasting pipx install ...
in this shell without the export gives command not found.
3. Install the agent
pipx install git+https://git.stormdevelopments.ca/official-public/storm-pulse.git
stormpulse --version # expect: a version number
If command not found, re-run the export PATH=... line from
step 2.
4. Enroll
Paste the Enroll command from the dashboard panel (step 1 above in the TL;DR). It looks roughly like:
stormpulse enroll \
https://stormdevelopments.ca/api/enroll/ \
YOUR_AGENT_ID \
YOUR_ENROLLMENT_TOKEN
— but with real values pre-filled by the dashboard. If you used the CLI fallback above, paste that output instead.
Writes credentials to ~/.config/stormpulse/. The private key never
leaves this machine — see Security Architecture
for the cert/CSR mechanics. Pass --creds-dir <path> if you want
them somewhere else.
Don't reach for
sudoto work around agent errors. The binary lives at~/.local/bin/stormpulseand sudo'ssecure_pathstrips that — you'll getcommand not found. The agent is rootless; sudo isn't the fix for any agent problem.
5. Init
cd "$PROJECT_DIR"
stormpulse init --user
Wizard prompts — see the TL;DR step 3 list.
Writes ~/.config/stormpulse/stormpulse.toml and
~/.config/systemd/user/stormpulse.service. Use --force to
overwrite existing files. Edit the toml later to
customize commands.
--user forces user-mode install (a user systemd unit). Without it,
init tries to auto-detect mode by probing
$XDG_RUNTIME_DIR/docker.sock. If rootless dockerd isn't running
yet, the probe misses and init falls back to system mode, which
then errors with "System-mode install needs root". Always pass
--user on hardened boxes — it's deterministic.
If the wizard refuses with "Rerun without sudo for user mode", drop sudo and try again.
6. Git remote (optional)
Skip if the project has no git repo, or your user can already
git pull it (SSH keys set up).
Otherwise, set an HTTPS remote with a Forgejo token:
GIT_TOKEN="forgejo-read-only-token" # Settings → Applications
REPO_PATH="ORG/REPO.git"
git -C "$PROJECT_DIR" remote set-url origin \
"https://${USER}:${GIT_TOKEN}@git.stormdevelopments.ca/${REPO_PATH}"
For a public repo, omit ${USER}:${GIT_TOKEN}@.
7. Start
sudo loginctl enable-linger $USER # only if "Linger=no"
systemctl --user daemon-reload
systemctl --user enable --now stormpulse
journalctl --user -u stormpulse -f
enable-linger lets the user unit survive your logout. Playbook 001
sets it for admin users — check with
loginctl show-user $USER | grep Linger.
The agent connects outbound only — no inbound ports needed. If your egress is restrictive, allow HTTPS (port 443) to the dashboard.
8. Container log shipping (optional)
stormpulse logging init
Detects running containers and writes one [[log_groups]] block per
container to ~/.config/stormpulse/stormpulse.toml. See
Log Shipping for parser choices and the full reference.
Host service logs
On hardened boxes, Caddy and fail2ban run on the host, not in
containers — their logs are files on disk. Add [[log_groups]]
entries to ~/.config/stormpulse/stormpulse.toml:
[[log_groups]]
name = "caddy"
source_type = "file"
path = "/var/log/caddy/access.log"
parser = "caddy_json"
[[log_groups]]
name = "fail2ban"
source_type = "file"
path = "/var/log/fail2ban.log"
parser = "stormpulse"
[[log_groups]]
name = "ufw"
source_type = "file"
path = "/var/log/ufw.log"
parser = "stormpulse"
Restart after editing:
systemctl --user restart stormpulse
The agent runs as your user, so it can only read files your user can
read. Playbook 001 puts admin users in the adm group, which grants
read on most /var/log/*. If a group is missing in the dashboard,
that's the first thing to check.
Group names matter for tab routing: caddy, fail2ban, ufw →
Network tab; system, audit, kernel, journald → Server;
everything else → Activity. (ADR storm-pulse CORE-003.)
Verify end-to-end
- Agent logs —
journalctl --user -u stormpulse -fshows "Connected to dashboard" plus heartbeat/metrics sends. - Dashboard — server status dot is green; CPU/memory/disk metrics appear.
- Test command — trigger a
git_pullfrom the dashboard. The agent log shows it executing; the result lands back in the dashboard.
Manual enrollment (rare)
Only when the VPS can't reach the dashboard during setup. The private key must be generated on this VPS — never imported.
mkdir -p ~/.config/stormpulse && chmod 700 ~/.config/stormpulse
cd ~/.config/stormpulse
openssl ecparam -genkey -name prime256v1 -noout -out agent-key.pem
chmod 600 agent-key.pem
AGENT_ID="your-host-name"
openssl req -new -key agent-key.pem -out /tmp/agent.csr -subj "/CN=$AGENT_ID"
Send /tmp/agent.csr to the CA operator. They return agent.pem
(signed cert) and ca.pem (CA cert). Drop both in
~/.config/stormpulse/ at mode 0644. Place the HMAC shared secret
as raw bytes at ~/.config/stormpulse/hmac.key at mode 0600. Then
skip to detail step 5 (Init).
Troubleshooting
| Symptom | Check |
|---|---|
stormpulse: command not found |
export PATH="$HOME/.local/bin:$PATH" — detail step 2 |
| Wizard says "Rerun without sudo for user mode" | You ran init as root. Drop sudo. |
| Init says "System-mode install needs root" | Missing --user flag. Auto-detection probes for rootless dockerd; if it's not running, init falls back to system mode. Always pass --user on hardened boxes. |
| Init says "Directory not found: /opt/foo" at the project-dir prompt | Open another shell. sudo mkdir -p /opt/foo && sudo chown $USER:$USER /opt/foo. Then re-enter the path at the still-open prompt. (Fixed in 0.1.9: wizard offers to create the dir; if the parent needs root, prints the exact sudo line.) |
| "Connection refused" in logs | Dashboard down, or wrong WebSocket URL |
| "SSL: CERTIFICATE_VERIFY_FAILED" | Wrong CA, or cert not signed by this CA |
| "Auth setup error" | ~/.config/stormpulse/hmac.key missing/empty/unreadable |
| Status stays "disconnected" | pulse_token mismatch with the Server record |
| Commands fail with "not_found" | docker ps doesn't work for your user — rootless dockerd not up |
| Metrics work but commands don't | HMAC key mismatch between dashboard and agent |
| Agent dies on logout | loginctl show-user $USER should show Linger=yes — detail step 7 |
Update
pipx upgrade storm-pulse-agent
systemctl --user restart stormpulse
journalctl --user -u stormpulse -f
If pipx upgrade finds no new version but you want main:
pipx install --force git+https://git.stormdevelopments.ca/official-public/storm-pulse.git
systemctl --user restart stormpulse