| name | deployment-automation |
| description | Guides deployment of Muni software to rovers (aarch64 cross-compilation, systemd) and depot (Docker Compose). Use when deploying firmware updates, installing services, configuring environments, troubleshooting deployment failures, or setting up new rovers/depot instances. Covers cross-compilation with `cross` tool, deploy.sh script usage, systemd service management, Docker Compose profiles, environment variables, and rollback procedures. |
| allowed-tools | Read, Grep, Glob, Bash(cross:build), Bash(cargo:build), Bash(scp), Bash(ssh), Bash(docker:*), Bash(systemctl) |
Deployment Automation Skill
Guides deployment of Muni software across heterogeneous platforms: rovers (ARM64) and depot (x86_64).
Overview
Muni has two deployment targets with different strategies:
| Target | Platform | Method | Build Tool | Deploy Tool |
|---|
| Rovers | aarch64 (ARM64) | Binary + systemd | cross / cargo | deploy.sh (scp + ssh) |
| Depot | x86_64 | Docker containers | Docker | docker-compose |
Key Files
Rover Deployment:
bvr/firmware/deploy.sh - Deployment script
bvr/firmware/config/bvr.toml - Runtime configuration
bvr/firmware/config/*.service - Systemd service units
Depot Deployment:
depot/docker-compose.yml - Service orchestration
depot/.env - Environment variables
depot/*/Dockerfile - Service images
Rover Deployment
Cross-Compilation Setup
Install Cross
Requirements:
- Rust 1.83+
- Docker (for cross)
cross tool
cargo install cross --git https://github.com/cross-rs/cross
cross --version
Why cross?
- Handles cross-compilation toolchains automatically
- Uses Docker to provide consistent build environment
- Supports aarch64-unknown-linux-gnu target
- Easier than setting up native cross-compilation
Alternative: Native cargo
rustup target add aarch64-unknown-linux-gnu
brew install aarch64-unknown-linux-gnu
cargo build --release --target aarch64-unknown-linux-gnu
⚠️ Native cross-compilation limitations:
- May fail with system dependencies (e.g., GStreamer)
- Requires manual toolchain setup
- Platform-specific quirks
✅ cross is recommended for reliability
Cross.toml Configuration
[build]
default-target = "aarch64-unknown-linux-gnu"
[target.aarch64-unknown-linux-gnu]
image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:latest"
Deploy Script Usage
Basic Deployment
cd bvr/firmware
./deploy.sh frog-0
./deploy.sh frog-0 --no-restart
./deploy.sh frog-0 --user admin
Full Deployment
./deploy.sh frog-0 --all
./deploy.sh frog-0 --cli --config --services --sync
Partial Deployment
./deploy.sh frog-0 --cli
./deploy.sh frog-0 --config
./deploy.sh frog-0 --services
./deploy.sh frog-0 --sync
Deploy Script Internals
What deploy.sh does:
-
Build Phase:
cross build --release --target aarch64-unknown-linux-gnu --bin bvrd
-
Pre-Deploy Phase:
ssh $REMOTE "sudo systemctl stop bvrd"
-
Deploy Phase:
scp target/aarch64-unknown-linux-gnu/release/bvrd $REMOTE:/tmp/
ssh $REMOTE "sudo mv /tmp/bvrd /usr/local/bin/ && sudo chmod +x /usr/local/bin/bvrd"
scp config/bvr.toml $REMOTE:/tmp/
ssh $REMOTE "sudo mv /tmp/bvr.toml /etc/bvr/"
scp config/*.service $REMOTE:/tmp/
ssh $REMOTE "sudo mv /tmp/*.service /etc/systemd/system/ && sudo systemctl daemon-reload"
-
Post-Deploy Phase:
ssh $REMOTE "sudo systemctl start bvrd"
ssh $REMOTE "sudo systemctl status bvrd"
Systemd Service Management
Service Units
bvrd.service (main daemon):
[Unit]
Description=BVR Daemon
After=network.target can.service
Requires=can.service
[Service]
Type=simple
User=cam
ExecStart=/usr/local/bin/bvrd
Restart=always
RestartSec=5
Environment="RUST_LOG=info"
WorkingDirectory=/home/cam
[Install]
WantedBy=multi-user.target
can.service (CAN bus setup):
[Unit]
Description=CAN Bus Setup
Before=bvrd.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/setup-can.sh
ExecStop=/usr/local/bin/teardown-can.sh
[Install]
WantedBy=multi-user.target
bvr-sync.service (session sync):
[Unit]
Description=BVR Session Sync
After=network.target
[Service]
Type=oneshot
User=cam
ExecStart=/usr/local/bin/bvr-sync
Environment="RUST_LOG=info"
bvr-sync.timer (periodic sync):
[Unit]
Description=BVR Session Sync Timer
Requires=bvr-sync.service
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target
Service Commands
ssh frog-0 "sudo systemctl start bvrd"
ssh frog-0 "sudo systemctl stop bvrd"
ssh frog-0 "sudo systemctl restart bvrd"
ssh frog-0 "sudo systemctl status bvrd"
ssh frog-0 "sudo systemctl enable bvrd"
ssh frog-0 "sudo systemctl disable bvrd"
ssh frog-0 "sudo journalctl -u bvrd -f"
ssh frog-0 "sudo journalctl -u bvrd -n 100"
ssh frog-0 "sudo systemctl daemon-reload"
Configuration Management
bvr.toml (runtime configuration):
[rover]
id = "frog-0"
name = "Frog Zero"
[network]
discovery_url = "http://10.0.0.1:4860"
teleop_bind = "0.0.0.0:4850"
video_bind = "0.0.0.0:4851"
[can]
interface = "can0"
bitrate = 500000
[gps]
port = "/dev/ttyUSB0"
baudrate = 115200
Deploying config changes:
vim bvr/firmware/config/bvr.toml
./deploy.sh frog-0 --config
./deploy.sh frog-0 --config
Troubleshooting Rover Deployment
Build Failures
cross not found:
cargo install cross --git https://github.com/cross-rs/cross
Docker not running:
Compilation errors:
cargo clean
cross build --release --target aarch64-unknown-linux-gnu
rustup show
rustup update
Deployment Failures
SSH connection failed:
ssh frog-0
tailscale status
ssh-add -l
Permission denied on rover:
ssh frog-0 "echo '$USER ALL=(ALL) NOPASSWD: /bin/systemctl, /bin/mv' | sudo tee /etc/sudoers.d/$USER"
Service won't start:
ssh frog-0 "sudo systemctl status bvrd"
ssh frog-0 "sudo journalctl -u bvrd -n 100"
ssh frog-0 "ls -l /usr/local/bin/bvrd"
ssh frog-0 "cat /etc/bvr/bvr.toml"
ssh frog-0 "/usr/local/bin/bvrd"
Rollback Procedure
ssh frog-0 "sudo cp /usr/local/bin/bvrd /usr/local/bin/bvrd.backup"
./deploy.sh frog-0
ssh frog-0 "sudo systemctl stop bvrd"
ssh frog-0 "sudo mv /usr/local/bin/bvrd.backup /usr/local/bin/bvrd"
ssh frog-0 "sudo systemctl start bvrd"
Depot Deployment
Docker Compose Deployment
Initial Setup
cd depot
cp .env.example .env
vim .env
.env file:
CONSOLE_PASSWORD=your_secure_password
GRAFANA_ADMIN_PASSWORD=your_grafana_password
INFLUXDB_ADMIN_TOKEN=your_influxdb_token
INFLUXDB_ORG=muni
INFLUXDB_BUCKET=muni
SESSIONS_PATH=/data/sessions
MAPS_PATH=/data/maps
RETENTION_DAYS=30
Build and Start
docker compose build
docker compose up -d
docker compose logs -f
docker compose ps
Service Profiles
Base services (always run):
- console (web UI)
- discovery (rover tracking)
- dispatch (mission planning)
- map-api (map serving)
- influxdb (metrics)
- grafana (dashboards)
- postgres (dispatch database)
- sftp (session sync)
GPU profile (optional):
docker compose --profile gpu up -d
RTK profile (optional):
docker compose --profile rtk up -d
Multiple profiles:
docker compose --profile gpu --profile rtk up -d
Service Updates
Update Single Service
git pull
docker compose build discovery
docker compose up -d discovery
docker compose logs -f discovery
Update All Services
git pull
docker compose build
docker compose up -d
docker compose ps
docker compose logs -f
Rolling Updates
for service in discovery dispatch map-api gps-status; do
echo "Updating $service..."
docker compose build $service
docker compose up -d $service
sleep 5
docker compose ps $service
done
Database Migrations
Dispatch service (PostgreSQL):
Migrations run automatically on service startup (see depot/dispatch/src/main.rs).
Manual migration (if needed):
docker compose exec postgres psql -U postgres -d dispatch
\i /path/to/migration.sql
docker compose restart dispatch
Reset database (DESTRUCTIVE):
docker compose down
docker volume rm depot_postgres-data
docker compose up -d
Environment Configuration
Updating Environment Variables
vim depot/.env
docker compose up -d
Which services need restart after .env changes?
- Console:
CONSOLE_PASSWORD, CONSOLE_USERNAME
- InfluxDB:
INFLUXDB_* variables
- Grafana:
GRAFANA_* variables
- Discovery:
SESSIONS_PATH
- Dispatch:
DATABASE_URL
Secrets Management
Development:
Production:
docker secret create database_password /path/to/secret.txt
services:
dispatch:
secrets:
- database_password
secrets:
database_password:
external: true
Troubleshooting Depot Deployment
Service Won't Start
docker compose logs -f <service>
docker compose ps <service>
docker inspect --format='{{.State.ExitCode}}' depot-<service>
docker compose run --rm <service> sh
Network Issues
docker network ls
docker network inspect depot_default
docker compose exec console ping discovery
docker compose exec console wget -O- http://discovery:4860/health
Volume Issues
docker volume ls
docker volume inspect depot_sessions-data
docker compose exec discovery ls -la /data/sessions
Health Check Failing
docker inspect depot-discovery | jq '.[0].State.Health'
docker compose exec discovery wget -O- http://localhost:4860/health
docker compose logs -f discovery
Port Conflicts
lsof -i :4860
ports:
- "4870:4860"
Out of Disk Space
df -h
docker system df
docker system prune -a --volumes
docker image prune -a
docker volume prune
Backup and Restore
Backup
docker run --rm \
-v depot_postgres-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/postgres-backup.tar.gz /data
cp depot/.env depot/.env.backup
tar czf depot-config-backup.tar.gz depot/.env depot/docker-compose.yml depot/grafana/
Restore
docker compose down
docker volume rm depot_postgres-data
docker volume create depot_postgres-data
docker run --rm \
-v depot_postgres-data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/postgres-backup.tar.gz -C /
tar xzf depot-config-backup.tar.gz
docker compose up -d
CI/CD Integration
GitHub Actions
name: Deploy Depot
on:
push:
branches: [main]
paths:
- 'depot/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build images
run: |
cd depot
docker compose build
- name: Push to registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
docker compose push
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /opt/depot
docker compose pull
docker compose up -d
GitLab CI
stages:
- build
- deploy
build:
stage: build
script:
- cd depot
- docker compose build
- docker compose push
deploy:
stage: deploy
only:
- main
script:
- ssh $DEPLOY_USER@$DEPLOY_HOST "cd /opt/depot && docker compose pull && docker compose up -d"
Deployment Checklists
New Rover Setup
New Depot Setup
Deployment Smoke Tests
Rover:
Depot:
References