Public example sister-repo for the
pi-manager-template Ansible automation.
It is scoped to engine stations only — installing and updating the wildfire-detection engine on a Raspberry Pi. Server roles (alert-api, platform, annotation, mediamtx, openvpn…) are out of scope.
⚠️ Everything in this repo is FAKE / placeholder data — IPs, passwords, tokens, the vault password, the SSH keys. Use it to learn the layout, then create your own privatepi-manager-Xrepo with real values.
Two repos work as a pair:
| Repo | Role |
|---|---|
pi-manager-template |
the playbooks / roles / make targets (the engine code). You never edit it. |
pi-manager-example (this repo) |
the inventory + vars + secrets for your fleet. The template reads it through REPO_PATH. |
Provisioning one engine is two steps:
- Provision the Pi with
scripts/setup_pi.sh(runs on the Pi). - Deploy the engine app with
make deploy-one-engine(runs from the template, pointed at this repo).
After flashing the SD card and booting the Pi, copy the script over and run it, passing the static IP you want this Pi to have:
# from your laptop, <pi-dhcp-ip> = the address the freshly-flashed Pi got on DHCP
scp scripts/setup_pi.sh pi@<pi-dhcp-ip>:~
ssh pi@<pi-dhcp-ip>
sudo ./setup_pi.sh 192.168.1.99 # <- the static IP to assign to this PiThis replaces the Ansible rpi-init.yml first-time init (which needs the
OpenVPN / mediamtx / reverse-ssh servers). It installs all the engine's system
dependencies — apt base, Docker + Compose, NetworkManager/Wi-Fi, OpenVPN
package, Grafana Alloy — and sets the static IP. The server-only steps (VPN
connection, stream registration, reverse-ssh tunnel) are skipped.
sudo ./setup_pi.sh 192.168.1.99 # set static IP
sudo ./setup_pi.sh # use the default 192.168.1.99
sudo STATIC_IP="" WIFI_SSID="" ./setup_pi.sh # keep DHCP, no Wi-Fi profile
sudo ./setup_pi.sh --help # all optionsAfter it finishes, reboot the Pi — it comes back on the static IP you passed.
Put that same IP in host_vars/example-station/vars.yml
(ansible_host + static_ip_address) so Step 2 can reach it.
On deploy, the engine logs in to the alert API and fetches one token per camera. So before deploying you set two things:
inventory/group_vars/envprod/vars.yml:
api_dns: "alertapi.example.org" # <- your alert API host (no https://, no trailing /)The engine role calls https://<api_dns>/api/v1/login/creds, so the API must
be reachable over HTTPS (put TLS in front of a local/test API).
The admin login/password used to obtain the API token come from environment
variables, set in pi-manager-template/.env:
SUPERADMIN_LOGIN=<your api admin login>
SUPERADMIN_PWD=<your api admin password>They must be valid on the API at api_dns.
The camera and Pi passwords live in the (fake, encrypted) vault files:
inventory/group_vars/engine_servers/vars.vault.yml→CAM_USER,CAM_PWDhost_vars/example-station/vars.vault.yml→ansible_password(the Pi user's password), camera creds, etc.
Edit them with the vault password in .vault_passwrd:
ansible-vault edit host_vars/example-station/vars.vault.yml
ansible-vault view inventory/group_vars/engine_servers/vars.vault.yml
ansible-vault rekey <file> # change the vault passwordThe cameras in
host_vars/example-station/vars.yml(config_json) must already exist in the API DB with the same IDs ("1","2"). Seed them with theinit_script/helpers in the template repo, run against your API.
Point the template at this repo. In pi-manager-template/.env:
REPO_PATH=../pi-manager-example
VAULT_PASSWORD_FILE=../pi-manager-example/.vault_passwrd
SSH_PRIVATE_KEY_FILE=../pi-manager-example/id_rsa
LIMIT=example-station
SUPERADMIN_LOGIN=<your api admin login>
SUPERADMIN_PWD=<your api admin password>Drop the SSH private key that can reach the Pi at
pi-manager-example/id_rsa (gitignored — never commit it).
Then, from the template repo:
cd ../pi-manager-template
make prepare # copies this repo's inventory/host_vars/group_vars in
cp -r group_vars/* inventory/group_vars/ # see "group_vars quirk" below
make deploy-one-engine SITE=example-stationgroup_vars quirk —
make preparecopies this repo'sinventory/group_vars/into the template's root./group_vars/, butansible-playbook -i inventory/hosts_prod(used bydeploy-one-engine) reads group_vars frominventory/group_vars/. So aftermake prepareyou must sync them:cp -r group_vars/* inventory/group_vars/. Otherwise stale group_vars are used andcheck_varsfails with "variable is empty".
deploy-one-engine runs playbooks/deploy-engines.yml (roles check_vars,
cron, engine, engine_cron). It does not touch the VPN / mediamtx /
reverse-ssh servers, so you only need:
- A network route to the Pi — without the VPN, be on the same LAN and set
ansible_hostto its local IP. - A reachable HTTPS alert API at
api_dnswith the cameras seeded. - Internet on the Pi — to
git clonepyro-engine and pull Docker images.
Note: the template's
ansible.cfgsetscheck_mode = yes, so runs are dry-run by default — remove that line for a real apply.
pi-manager-example/
├── .vault_passwrd # vault password (FAKE — committed so the example runs)
├── id_rsa # SSH PRIVATE key to your Pi — NOT committed, you provide it
├── ansible.cfg
├── scripts/
│ └── setup_pi.sh # Step 1 — standalone Pi provisioning (takes the static IP)
├── ssh_keys/ # public keys (only used by the full rpi-init path, not the example flow)
│ ├── mateo_to_pyrobastion.pub
│ └── felix_to_pyrobastion.pub
├── inventory/
│ ├── hosts_prod # one engine + minimal backing-server stubs
│ ├── host_vars/ # (kept empty — host_vars live at repo root, see prepare)
│ └── group_vars/
│ ├── all/{vars.yml,vars.vault.yml}
│ ├── engine_servers/{vars.yml,vars.vault.yml}
│ └── envprod/vars.yml # api_dns, S3, OpenVPN, mediamtx
└── host_vars/
└── example-station/ # the example engine station
├── vars.yml # config_json (cameras) + static IP
└── vars.vault.yml # credentials (FAKE)
make prepare(in the template) copiesinventory/hosts*→./inventory/,host_vars→./host_vars, andinventory/group_vars→./group_vars. That is whyhost_vars/lives at the repo root andgroup_vars/lives underinventory/.
example-station:
ansible_host: 192.168.1.42(change to your Pi's address)static_ip_address: 192.168.1.99(the IP you passed tosetup_pi.sh)- two example PTZ cameras at
192.168.1.11/192.168.1.12(IDs1/2)