Table of Contents
Agent Setup Guide
How to install and configure the Storm Pulse agent on a new VPS.
0. Before you start (on the dashboard server)
Run this on the machine hosting the Storm Pulse dashboard:
python manage.py create_enrollment_token YOUR_HOST_NAME
Or the docker version:
docker compose --env-file .env -f docker/docker-compose.prod.yml exec web python manage.py create_enrollment_token YOUR_HOST_NAME
Example output:
Enrollment token for Storm Forge (git.stormdevelopments.ca)
Enrollment token: 2251c816-7980-482d-8b51-00b113dfbc47
Expires: 2026-02-24 00:55:41 UTC (24h)
Pulse token: None
Enroll with:
sudo /opt/stormpulse/venv/bin/stormpulse enroll \
https://stormdevelopments.ca/api/enroll/ \
git.stormdevelopments.ca \
a1b2c3d4-5678-9abc-def0-222222222222
You need two values from this output for the VPS setup:
- Pulse token — permanent auth token for this server. Goes in the TOML config.
- Enrollment token — one-time token that burns after use. Used in the
stormpulse enrollcommand.
Copy the whole output somewhere. You'll need it below.
The hostname (e.g. stormdevelopments.ca) is the agent-id. It matches the Server record in the dashboard.
On the VPS
1. Create the system user
sudo useradd --system --shell /usr/sbin/nologin --home-dir /opt/stormpulse stormpulse
2. Create directories
sudo mkdir -p /opt/stormpulse/{venv,data,.docker}
sudo mkdir -p /etc/stormpulse
sudo chown stormpulse:stormpulse /opt/stormpulse/data /opt/stormpulse/.docker
sudo chown root:stormpulse /etc/stormpulse
sudo chmod 750 /etc/stormpulse
| Path | Purpose | Owner |
|---|---|---|
/opt/stormpulse/venv/ |
Python virtual environment | root |
/opt/stormpulse/data/ |
SQLite nonce database | stormpulse |
/opt/stormpulse/.docker/ |
Docker config cache | stormpulse |
/etc/stormpulse/ |
Config + credentials | root (read-only for stormpulse) |
3. Install Python 3.12+
python3 --version
If 3.12 or higher (Ubuntu 24.04+):
sudo apt install python3-venv -y
sudo python3 -m venv /opt/stormpulse/venv
If 3.10 or 3.11 (Ubuntu 22.04), install 3.12 from the Deadsnakes PPA:
sudo add-apt-repository ppa:deadsnakes/ppa -y
sudo apt install python3.12 python3.12-venv python3.12-dev -y
sudo python3.12 -m venv /opt/stormpulse/venv
python3 still points to 3.10 on Ubuntu 22.04. Always use python3.12 explicitly.
4. Install the agent
sudo /opt/stormpulse/venv/bin/pip install git+https://git.stormdevelopments.ca/official-public/storm-pulse.git
Verify:
/opt/stormpulse/venv/bin/stormpulse --version
5. Enroll
Paste the stormpulse enroll command from the create_enrollment_token output, prefixed with the venv path:
sudo /opt/stormpulse/venv/bin/stormpulse enroll \
https://stormdevelopments.ca/api/enroll/ \
COPY_AGENT_ID_HERE \
COPY_ENROLLMENT_TOKEN_HERE
This generates an EC P-256 keypair locally, sends a CSR to the dashboard, and writes four files to /etc/stormpulse/. The private key never leaves this machine.
| File | Contents | Mode |
|---|---|---|
agent-key.pem |
EC private key (generated locally) | 0640 root:stormpulse |
agent.pem |
Signed client certificate | 0644 root:stormpulse |
ca.pem |
CA certificate | 0644 root:stormpulse |
hmac.key |
HMAC shared secret | 0640 root:stormpulse |
Ownership and permissions are set automatically by enroll. No manual chown needed.
6. Initialize
Run stormpulse init from your project directory. It generates the TOML config, creates the systemd unit, adds stormpulse to the docker group, and sets project directory permissions.
# Set the project directory you want stormpulse to be active on
PROJECT_DIR="/opt/your-project"
cd ${PROJECT_DIR}
sudo /opt/stormpulse/venv/bin/stormpulse init
The wizard prompts for:
| Prompt | Where to find it |
|---|---|
| Pulse token | The create_enrollment_token output from step 0 |
| Dashboard WebSocket URL | Auto-derived from enrollment endpoint, confirm or edit |
| Project directory | Defaults to current directory (the cd above) |
| Compose file | Auto-detected from the project directory |
| Docker service name | Parsed from the compose file |
| .env file | Auto-detected, or skip |
The project directory you choose here is saved as project.project_dir in the generated config. All deploy commands (git_pull, etc.) run against this directory.
It writes:
/etc/stormpulse/stormpulse.toml(mode 0640, root:stormpulse)/etc/systemd/system/stormpulse.service(mode 0644)
It also runs: usermod -aG docker stormpulse, git config --system --add safe.directory, and sets project directory permissions (with Docker volume directories excluded from the recursive chown).
Use --force to overwrite existing files. Use --creds-dir if credentials are not in /etc/stormpulse.
Optional: Learn how to customize commands by editing the generated config.
7. Git remote URL
Set the remote to HTTPS (the stormpulse system user has no SSH keys). Use the same project directory you set during stormpulse init:
If the repo is public:
- Set the Repo URL variable
REPO_URL="https://git.stormdevelopments.ca/ORG/REPO.git"
- Set the remote origin URL
git -C "$PROJECT_DIR" remote set-url origin "$REPO_URL"
If the repo is private:
Create a Forgejo access token at Settings → Applications with read-only repository permissions.
- Set the Git Token variable
GIT_TOKEN="your-forgejo-access-token"
- Set the Repo URL variable
REPO_URL="https://stormpulse:${GIT_TOKEN}@git.stormdevelopments.ca/ORG/REPO.git"
- Set the remote origin URL
git -C "$PROJECT_DIR" remote set-url origin "$REPO_URL"
8. Start
sudo systemctl enable --now stormpulse
Check:
sudo journalctl -u stormpulse -f
The agent connects outbound only — no inbound ports needed. If you have restrictive outbound rules, allow HTTPS (port 443) to the dashboard host.
Verify
- Agent logs —
journalctl -u stormpulse -fshould show "Connected to dashboard" and periodic heartbeat/metrics sends. - Dashboard — The server's status dot turns green. CPU, memory, and disk metrics appear.
- Test command — Trigger a
git_pullfrom the dashboard. The agent log shows the command executing and the result sent back.
Certificates (manual alternative)
If you cannot use stormpulse enroll (e.g. no network access to the dashboard during setup), you can provision credentials manually. The private key must be generated on the VPS itself.
# 1. Generate the private key ON THIS MACHINE
openssl ecparam -genkey -name prime256v1 -noout -out /etc/stormpulse/agent-key.pem
sudo chmod 600 /etc/stormpulse/agent-key.pem
# 2. Create a CSR (send this to whoever manages the CA)
openssl req -new -key /etc/stormpulse/agent-key.pem -out /tmp/agent.csr -subj "/CN=stormdevelopments.ca"
# 3. Get the CSR signed by the CA — returns agent.pem and ca.pem
# 4. Place the HMAC key as raw bytes
sudo chown root:stormpulse /etc/stormpulse/agent-key.pem /etc/stormpulse/hmac.key
sudo chown root:stormpulse /etc/stormpulse/agent.pem /etc/stormpulse/ca.pem
sudo chmod 640 /etc/stormpulse/agent-key.pem /etc/stormpulse/hmac.key
sudo chmod 644 /etc/stormpulse/agent.pem /etc/stormpulse/ca.pem
Troubleshooting
| Symptom | Check |
|---|---|
| "Connection refused" in logs | Is the dashboard running? Is the WebSocket URL correct? |
| "SSL: CERTIFICATE_VERIFY_FAILED" | Is ca.pem the correct CA cert? Is agent.pem signed by that CA? |
| "Auth setup error" | Is hmac.key readable by the stormpulse user? Is it non-empty? |
| "Configuration error: Missing files" | Run stormpulse run /etc/stormpulse/stormpulse.toml manually to see which paths are missing. |
| Status stays "disconnected" | Check pulse_token matches the Server record. Check agent logs for connection errors. |
| Commands fail with "not_found" | Is docker/git installed? Is stormpulse in the docker group? Did you restart the service after adding the group? |
| Metrics show but commands don't work | HMAC key mismatch between dashboard and agent. |
Update
# Update the agent
sudo /opt/stormpulse/venv/bin/pip install --force-reinstall --no-deps \
git+https://git.stormdevelopments.ca/official-public/storm-pulse.git
# Restart
sudo systemctl restart stormpulse
# Verify
sudo journalctl -u stormpulse -f