Repeatable, SSH-first provisioning for any new Raspberry Pi with:
- strong defaults
- zero plaintext secrets in git
- Bitwarden-backed secret retrieval
- BASB-compatible workstation tooling (
ark-auth-safe,ark-auth,bw,gh,uv)
Bring a new Pi online in 10-15 minutes with the same hardened baseline every time.
- Key-based SSH only (
PasswordAuthentication no) PermitRootLogin no- Dedicated admin user (no shared default user)
- UFW default deny inbound, allow SSH only (or your mgmt subnet)
fail2ban+unattended-upgrades- No secrets in repo,
.env, shell history, or long-lived files - Bitwarden item names are stable and per-device
BW_SESSIONis ephemeral (shell only), never committed or persisted
Use this structure in pi/:
pi/
README.md
inventory/
devices.yaml
scripts/
preflight.sh
prepare-image.sh
bootstrap-pi.sh
harden-ssh.sh
verify-pi.sh
sync-repos.sh
templates/
device.env.example
sshd_config.d-99-hardening.conf
This toolkit assumes your control machine already follows BASB:
cd ~/Git/BASB
bash install.sh --with-claude --with-docker
source ~/.bashrc
ark-auth-safeIf Claude Code reports Login successful but /init or claude -p still fails with Invalid bearer token, the usual cause is a stale apiKeyHelper override in ~/.claude/settings.json. Clear that file to {} for Claude.ai/OAuth login, or keep it only if you intentionally use BASB's Bitwarden/API-key mode with CLAUDE_BW_ITEM=claude.api_key.
Required CLIs:
ssh,scpghbwjqyq(optional, useful for inventory parsing)
If your control machine was bootstrapped with ~/Git/BASB/install.sh, jq and yq are installed automatically. If you are not using BASB, install jq locally before running make:
sudo apt-get update
sudo apt-get install -y jqUse profile/device-scoped items so automation is deterministic:
agent.pi.<device>.wifiagent.pi.<device>.bootstrapagent.pi.<device>.github.pat(optional, if device needs GH API access)agent.pi.<device>.tailscale.authkey(optional)
Example:
agent.pi.edge-01.bootstrap
Track all Pis in one file: inventory/devices.yaml
devices:
- name: edge-01
hostname: pi-edge-01
role: edge
ip: 192.168.1.61
ssh_user: ops
ssh_port: 22
bw_prefix: agent.pi.edge-01
repos:
- git@github.com:RickArko/BASB.git
- git@github.com:RickArko/RickArko.git- Create/update device entry in
inventory/devices.yaml. - Flash Raspberry Pi OS Lite (64-bit) and pre-seed:
- hostname (
pi-edge-01) - non-default admin user (
ops) - your SSH public key
- disable password auth after first successful key login
- hostname (
- Boot Pi and find IP from router/ARP table.
- From your control machine, run bootstrap from the repo root:
cd /home/ricka/Git/PiCLI
DEVICE=edge-01
./scripts/preflight.sh
./scripts/bootstrap-pi.sh --device "$DEVICE"
./scripts/verify-pi.sh --device "$DEVICE"
./scripts/sync-repos.sh --device "$DEVICE"Or run the full flow with Make:
cd /home/ricka/Git/PiCLI
make provision DEVICE=edge-01preflight runs locally on the control machine inside /home/ricka/Git/PiCLI. It checks for local dependencies such as jq before any SSH connection to the Pi is attempted.
scripts/bootstrap-pi.sh should be safe to run repeatedly:
- run apt update/upgrade
- install baseline packages:
git,ufw,fail2ban,unattended-upgrades,ca-certificates,jq - apply SSH hardening config in
/etc/ssh/sshd_config.d/99-hardening.conf - reload sshd safely (validate config before restart)
- configure firewall policy
- set timezone/locale if provided
- write audit marker:
/var/lib/pi-toolkit/bootstrap.version
scripts/verify-pi.sh should fail fast if any baseline control is missing.
sshd -treturns cleansshd -T | grep -E "passwordauthentication|permitrootlogin"shows:passwordauthentication nopermitrootlogin no
ufw statusshows default deny inbound + ssh allow rulesystemctl is-enabled unattended-upgradesis enabledfail2ban-client statusis healthy
- Retrieve secrets only at runtime from Bitwarden:
bw get password <item> - Never save
BW_PASSWORDin files - Never commit
.envwith secrets - Never copy private keys into repo
If running headless automation:
BW_PASSWORD='<bitwarden-master-password>' ark-auth
unset BW_PASSWORD- Re-run
bootstrap-pi.shmonthly (or on baseline version bump) - Run
verify-pi.shafter every OS update - Keep per-device notes in Bitwarden item notes, not in git
- Rotate SSH keys and API tokens on schedule
scripts/preflight.shscripts/bootstrap-pi.shscripts/verify-pi.shscripts/sync-repos.shscripts/prepare-image.sh(optional automation layer)
This gives you a secure baseline quickly, then you can add role-specific layers (k3s, inference stack, cameras, sensors) on top of a known-good foundation.
ark-auth-safe
bw sync
# set local token
export BW_SESSION="$(bw unlock --raw)"
bw sync --session "$BW_SESSION"
ssh "$PI_USER@$PI_HOST" 'sudo apt update && sudo apt -y full-upgrade && sudo apt -y install git ufw fail2ban unattended-upgrades ca-certificates jq'
scp /home/ricka/Git/pi/templates/sshd_config.d-99-hardening.conf "$PI_USER@$PI_HOST:/tmp/99-hardening.conf"
ssh "$PI_USER@$PI_HOST" 'sudo install -m 0644 /tmp/99-hardening.conf /etc/ssh/sshd_config.d/99-hardening.conf && sudo sshd -t && sudo systemctl reload ssh'
ssh "$PI_USER@$PI_HOST" 'sudo ufw default deny incoming && sudo ufw default allow outgoing && sudo ufw allow 22/tcp && sudo ufw --force enable'
ssh "$PI_USER@$PI_HOST" 'sudo systemctl enable --now fail2ban unattended-upgrades'
ssh "$PI_USER@$PI_HOST" 'sudo sshd -T | grep -E "passwordauthentication|permitrootlogin"'
ssh "$PI_USER@$PI_HOST" 'sudo ufw status verbose'
ssh "$PI_USER@$PI_HOST" 'systemctl is-enabled unattended-upgrades && sudo fail2ban-client status'
ssh "$PI_USER@$PI_HOST" 'sudo sshd -T | grep -E "passwordauthentication|permitrootlogin"'
ssh "$PI_USER@$PI_HOST" 'sudo ufw status verbose'
ssh "$PI_USER@$PI_HOST" 'systemctl is-enabled unattended-upgrades && sudo fail2ban-client status'
ssh "$PI_USER@$PI_HOST" 'mkdir -p ~/Git && cd ~/Git && git clone git@github.com:RickArko/BASB.git && git clone git@github.com:RickArko/RickArko.git'
IP=XXXX # e.g. 192.168.1.245
DEVICE=charles
cd ~/Git/PiCLI
ssh charles@$IP
make preflight
make provision DEVICE=$DEVICETop Priority Secrets like the Bitwarden master-password lookups happen on your laptop (the control machine) before anything is sent to the Pi.
- Laptop:
bw get password claude.api_key→ retrieves key from local vault - Laptop: builds the
openclaw onboard --anthropic-api-key <key>... command - Laptop → Pi: sends that command over
SSH
The Pi only ever sees the individual API keys (ideally DEVICE-scoped), never the master password or BW_SESSION token. The SSH heredoc runs on the remote side, but the Bitwarden calls all happen locally in install-openclaw.sh before the SSH session that runs openclaw onboard.