33 Setup Guide
mathew edited this page 2026-02-23 01:02:22 +00:00

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 enroll command.

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:

  1. Set the Repo URL variable
REPO_URL="https://git.stormdevelopments.ca/ORG/REPO.git"
  1. 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.

  1. Set the Git Token variable
GIT_TOKEN="your-forgejo-access-token"
  1. Set the Repo URL variable
REPO_URL="https://stormpulse:${GIT_TOKEN}@git.stormdevelopments.ca/ORG/REPO.git"
  1. 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

  1. Agent logsjournalctl -u stormpulse -f should show "Connected to dashboard" and periodic heartbeat/metrics sends.
  2. Dashboard — The server's status dot turns green. CPU, memory, and disk metrics appear.
  3. Test command — Trigger a git_pull from 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