Skip to content

ArkoLabs/PiCLI

Repository files navigation

Raspberry Pi Secure Setup Toolkit

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)

Goal

Bring a new Pi online in 10-15 minutes with the same hardened baseline every time.

Security model (opinionated)

  • 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_SESSION is ephemeral (shell only), never committed or persisted

Repo design

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

Dependencies (control machine)

This toolkit assumes your control machine already follows BASB:

cd ~/Git/BASB
bash install.sh --with-claude --with-docker
source ~/.bashrc
ark-auth-safe

If 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, scp
  • gh
  • bw
  • jq
  • yq (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 jq

Bitwarden naming convention

Use profile/device-scoped items so automation is deterministic:

  • agent.pi.<device>.wifi
  • agent.pi.<device>.bootstrap
  • agent.pi.<device>.github.pat (optional, if device needs GH API access)
  • agent.pi.<device>.tailscale.authkey (optional)

Example:

  • agent.pi.edge-01.bootstrap

Device inventory

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

Fast path for a new Pi

  1. Create/update device entry in inventory/devices.yaml.
  2. 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
  3. Boot Pi and find IP from router/ARP table.
  4. 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-01

preflight 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.

What bootstrap should do (idempotent)

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.

Recommended hardening checks

  • sshd -t returns clean
  • sshd -T | grep -E "passwordauthentication|permitrootlogin" shows:
    • passwordauthentication no
    • permitrootlogin no
  • ufw status shows default deny inbound + ssh allow rule
  • systemctl is-enabled unattended-upgrades is enabled
  • fail2ban-client status is healthy

Secrets handling rules

  • Retrieve secrets only at runtime from Bitwarden: bw get password <item>
  • Never save BW_PASSWORD in files
  • Never commit .env with secrets
  • Never copy private keys into repo

If running headless automation:

BW_PASSWORD='<bitwarden-master-password>' ark-auth
unset BW_PASSWORD

Operational lifecycle

  • Re-run bootstrap-pi.sh monthly (or on baseline version bump)
  • Run verify-pi.sh after every OS update
  • Keep per-device notes in Bitwarden item notes, not in git
  • Rotate SSH keys and API tokens on schedule

Minimal implementation order

  1. scripts/preflight.sh
  2. scripts/bootstrap-pi.sh
  3. scripts/verify-pi.sh
  4. scripts/sync-repos.sh
  5. scripts/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'

Example End to End Setup: DEVICE=charles


  IP=XXXX         # e.g. 192.168.1.245
  DEVICE=charles
  cd ~/Git/PiCLI

  ssh charles@$IP
  make preflight
  make provision DEVICE=$DEVICE

Secure Credentials Management


Top Priority Secrets like the Bitwarden master-password lookups happen on your laptop (the control machine) before anything is sent to the Pi.

Bitwarden Secret Management Flow:

  1. Laptop: bw get password claude.api_keyretrieves key from local vault
  2. Laptop: builds the openclaw onboard --anthropic-api-key <key> ... command
  3. 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.

About

CLI for a Pi

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors