Files
blackroad/bin/br-hw
Alexa Amundson 78fbe80f2a Initial monorepo — everything BlackRoad in one place
bin/       230 CLI tools (ask-*, br-*, agent-*, roadid, carpool)
scripts/   99 automation scripts
fleet/     Node configs and deployment
workers/   Cloudflare Worker sources (roadpay, road-search, squad webhooks)
roadc/     RoadC programming language
roadnet/   Mesh network (5 APs, WireGuard)
operator/  Memory system scripts
config/    System configs
dotfiles/  Shell configs
docs/      Documentation

BlackRoad OS — Pave Tomorrow.

RoadChain-SHA2048: d1a24f55318d338b
RoadChain-Identity: alexa@sovereign
RoadChain-Full: d1a24f55318d338b24b60bad7be39286379c76ae5470817482100cb0ddbbcb97e147d07ac7243da0a9f0363e4e5c833d612b9c0df3a3cd20802465420278ef74875a5b77f55af6fe42a931b8b635b3d0d0b6bde9abf33dc42eea52bc03c951406d8cbe49f1a3d29b26a94dade05e9477f34a7d4d4c6ec4005c3c2ac54e73a68440c512c8e83fd9b1fe234750b898ef8f4032c23db173961fe225e67a0432b5293a9714f76c5c57ed5fdf35b9fb40fd73c03ebf88b7253c6a0575f5afb6a6b49b3bda310602fb1ef676859962dad2aebbb2875814b30eee0a8ba195e482d4cbc91d8819e7f38f6db53e8063401649c77bb994371473cabfb917fb53e8cbe73d60
2026-03-14 17:08:41 -05:00

370 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
# br-hw - BlackRoad OS Hardware Profile Manager
# Detect, configure, and manage custom hardware across the fleet
# Usage: br-hw <detect|list|apply|add|scan> [args]
set -euo pipefail
source "$HOME/.blackroad/config/nodes.sh" 2>/dev/null || true
HW_DIR="$HOME/.blackroad/hardware"
mkdir -p "$HW_DIR/profiles" "$HW_DIR/detected"
usage() {
cat <<EOF
${PINK}br-hw${RESET} - BlackRoad OS Hardware Manager
${BLUE}USAGE:${RESET}
br-hw detect [node] Auto-detect hardware on a node
br-hw list List all hardware profiles
br-hw scan Scan fleet hardware
br-hw add <name> Create a custom hardware profile
br-hw apply <profile> [node] Apply a hardware profile to a node
br-hw info <node> Show detected hardware for a node
${AMBER}HARDWARE PROFILES:${RESET}
Profiles define device-specific configuration:
- Power settings (governor, clock speed, GPU memory)
- Peripheral support (Hailo, NVMe, cameras, I2C)
- Service assignments (what runs where)
- Custom udev rules and kernel parameters
${GREEN}EXAMPLES:${RESET}
br-hw detect cecilia Probe Cecilia's hardware
br-hw add my-pi5-hailo Create custom profile
br-hw apply ai-accelerator cecilia
br-hw scan Survey full fleet
EOF
}
# Detect hardware on a node via SSH
cmd_detect() {
local node="${1:-}"
if [[ -z "$node" ]]; then
# Detect local (Mac)
printf '%bDetecting local hardware...%b\n' "$AMBER" "$RESET"
detect_local
return
fi
printf '%bDetecting hardware on %s...%b\n' "$AMBER" "$node" "$RESET"
if ! br_ssh_up "$node" 2>/dev/null; then
printf '%b%s is offline%b\n' "$RED" "$node" "$RESET"
return 1
fi
local out_file="$HW_DIR/detected/${node}.yaml"
# Collect hardware info via SSH
local hw_info
hw_info=$(br_ssh "$node" "
echo '---'
echo 'node: $node'
echo 'detected: $(date -u +%Y-%m-%dT%H:%M:%SZ)'
# Platform
echo 'platform:'
echo ' model: \"'$(cat /proc/device-tree/model 2>/dev/null | tr -d '\0' || echo 'unknown')'\"'
echo ' arch: \"'$(uname -m)'\"'
echo ' kernel: \"'$(uname -r)'\"'
echo ' os: \"'$(. /etc/os-release 2>/dev/null && echo \"\$PRETTY_NAME\" || echo 'unknown')'\"'
# CPU
echo 'cpu:'
echo ' cores: '$(nproc)
echo ' governor: \"'$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null || echo 'unknown')'\"'
echo ' freq_mhz: '$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq 2>/dev/null | awk '{printf \"%.0f\", \$1/1000}' || echo '0')
echo ' max_freq_mhz: '$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq 2>/dev/null | awk '{printf \"%.0f\", \$1/1000}' || echo '0')
# Memory
echo 'memory:'
echo ' total_mb: '$(free -m | awk '/Mem:/{print \$2}')
echo ' available_mb: '$(free -m | awk '/Mem:/{print \$7}')
echo ' swap_mb: '$(free -m | awk '/Swap:/{print \$2}')
# Storage
echo 'storage:'
lsblk -dn -o NAME,SIZE,TYPE,TRAN 2>/dev/null | while read -r dev size type tran; do
echo \" - device: /dev/\$dev\"
echo \" size: \\\"\$size\\\"\"
echo \" type: \\\"\$type\\\"\"
echo \" transport: \\\"\${tran:-unknown}\\\"\"
done
# GPU
echo 'gpu:'
echo ' mem_mb: '$(vcgencmd get_config gpu_mem 2>/dev/null | cut -d= -f2 || echo '0')
# Temperature
echo 'thermal:'
echo ' temp_c: '$(vcgencmd measure_temp 2>/dev/null | grep -oP '[0-9.]+' || echo '0')
echo ' throttled: \"'$(vcgencmd get_throttled 2>/dev/null | cut -d= -f2 || echo 'unknown')'\"'
# Voltage
echo ' voltage_v: '$(vcgencmd measure_volts 2>/dev/null | grep -oP '[0-9.]+' || echo '0')
# Accelerators
echo 'accelerators:'
if [ -e /dev/hailo0 ]; then
echo ' - type: hailo-8'
echo ' device: /dev/hailo0'
serial=\$(hailortcli fw-control identify 2>/dev/null | grep -i serial | awk '{print \$NF}' || echo 'unknown')
echo \" serial: \\\"\$serial\\\"\"
echo ' tops: 26'
fi
# USB devices
echo 'usb_devices:'
lsusb 2>/dev/null | grep -v 'root hub' | while read -r line; do
echo \" - \\\"\$line\\\"\"
done
# I2C devices
echo 'i2c_devices:'
for bus in /dev/i2c-*; do
[ -e \"\$bus\" ] || continue
bus_num=\${bus##*-}
i2cdetect -y \"\$bus_num\" 2>/dev/null | grep -oP '[0-9a-f]{2}' | while read -r addr; do
echo \" - bus: \$bus_num\"
echo \" address: 0x\$addr\"
done
done 2>/dev/null
# Network
echo 'network:'
for iface in eth0 wlan0; do
if ip addr show \"\$iface\" &>/dev/null; then
ip_addr=\$(ip -4 addr show \"\$iface\" 2>/dev/null | grep -oP 'inet \K[0-9.]+' | head -1)
echo \" \$iface: \\\"\${ip_addr:-down}\\\"\"
fi
done
# Docker
echo 'docker:'
if command -v docker &>/dev/null; then
echo ' installed: true'
echo ' containers: '$(docker ps -q 2>/dev/null | wc -l)
else
echo ' installed: false'
fi
# Services
echo 'services:'
for svc in ollama docker tailscaled cloudflared nginx postgresql; do
if systemctl is-active \"\$svc\" &>/dev/null; then
echo \" \$svc: active\"
fi
done
" 2>/dev/null) || {
printf '%bFailed to detect hardware on %s%b\n' "$RED" "$node" "$RESET"
return 1
}
echo "$hw_info" > "$out_file"
printf '%bHardware profile saved: %s%b\n' "$GREEN" "$out_file" "$RESET"
# Display summary
echo "$hw_info"
}
detect_local() {
local out_file="$HW_DIR/detected/mac.yaml"
cat > "$out_file" <<YAML
---
node: mac
detected: $(date -u +%Y-%m-%dT%H:%M:%SZ)
platform:
model: "$(sysctl -n hw.model 2>/dev/null || echo 'Mac')"
arch: "$(uname -m)"
kernel: "$(uname -r)"
os: "$(sw_vers -productName 2>/dev/null) $(sw_vers -productVersion 2>/dev/null)"
cpu:
cores: $(sysctl -n hw.ncpu 2>/dev/null || echo 0)
brand: "$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo 'unknown')"
memory:
total_mb: $(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1048576 ))
storage:
- device: /dev/disk0
size: "$(diskutil info / 2>/dev/null | grep 'Disk Size' | awk -F: '{print $2}' | xargs || echo 'unknown')"
docker:
installed: $(command -v docker &>/dev/null && echo true || echo false)
containers: $(docker ps -q 2>/dev/null | wc -l | tr -d ' ')
YAML
printf '%bLocal hardware saved: %s%b\n' "$GREEN" "$out_file" "$RESET"
cat "$out_file"
}
cmd_list() {
printf '%b%-20s %s%b\n' "$BLUE" "PROFILE" "DESCRIPTION" "$RESET"
printf '%-20s %s\n' "───────" "───────────"
for f in "$HW_DIR/profiles/"*.yaml; do
[[ -f "$f" ]] || continue
name=$(basename "$f" .yaml)
desc=$(grep '^description:' "$f" 2>/dev/null | sed 's/^description: *//' | head -1)
printf '%-20s %s\n' "$name" "${desc:-}"
done
echo ""
printf '%bDetected:%b\n' "$AMBER" "$RESET"
for f in "$HW_DIR/detected/"*.yaml; do
[[ -f "$f" ]] || continue
name=$(basename "$f" .yaml)
detected=$(grep '^detected:' "$f" 2>/dev/null | awk '{print $2}')
printf ' %-16s (scanned %s)\n' "$name" "${detected:-never}"
done
}
cmd_add() {
local name="$1"
local profile="$HW_DIR/profiles/${name}.yaml"
if [[ -f "$profile" ]]; then
echo "Profile '$name' already exists."
return 1
fi
cat > "$profile" <<YAML
# BlackRoad OS Hardware Profile: $name
name: $name
description: Custom hardware profile
version: 1.0.0
# Target platform constraints
platform:
types: [pi5, pi4, pi400] # or: [x86, mac, any]
min_ram: 2048 # MB
required_devices: [] # e.g., [hailo, nvme]
# Power configuration
power:
governor: conservative # ondemand, conservative, performance, powersave
max_freq_mhz: 2000
gpu_mem_mb: 16
swappiness: 10
# Kernel parameters (appended to /boot/firmware/cmdline.txt)
kernel_params: []
# - "isolcpus=3"
# - "nohz_full=3"
# Sysctl overrides
sysctl:
vm.swappiness: 10
vm.dirty_ratio: 40
# net.core.somaxconn: 4096
# Services to enable/disable
services:
enable: []
# - ollama
# - docker
disable: []
# - cups
# - avahi-daemon
# udev rules (deployed to /etc/udev/rules.d/)
udev_rules: []
# - 'SUBSYSTEM=="usb", ATTR{idVendor}=="1a86", MODE="0666"'
# Custom setup commands (run once on apply)
setup_commands: []
# - "pip3 install hailort"
# Cron jobs to install
cron: []
# - "*/5 * * * * /opt/blackroad/bin/power-monitor.sh"
YAML
printf '%bCreated profile: %s%b\n' "$GREEN" "$profile" "$RESET"
printf 'Edit the profile: %s\n' "$profile"
}
cmd_apply() {
local profile_name="$1"
local node="${2:-}"
local profile="$HW_DIR/profiles/${profile_name}.yaml"
if [[ ! -f "$profile" ]]; then
echo "Profile '$profile_name' not found."
return 1
fi
if [[ -z "$node" ]]; then
echo "Usage: br-hw apply <profile> <node>"
return 1
fi
printf '%bApplying profile %s to %s...%b\n' "$AMBER" "$profile_name" "$node" "$RESET"
if ! br_ssh_up "$node" 2>/dev/null; then
printf '%b%s is offline%b\n' "$RED" "$node" "$RESET"
return 1
fi
# Copy profile to node
local ssh_target
ssh_target="$(br_ssh_target "$node")"
scp -q "$profile" "$ssh_target:/tmp/br-hw-profile.yaml" 2>/dev/null
# Apply power settings
local governor
governor=$(grep 'governor:' "$profile" | head -1 | awk '{print $2}')
if [[ -n "$governor" && "$governor" != "null" ]]; then
printf ' governor → %s: ' "$governor"
br_ssh "$node" "echo '$governor' | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor >/dev/null 2>&1" && \
printf '%bok%b\n' "$GREEN" "$RESET" || printf '%bfail%b\n' "$RED" "$RESET"
fi
# Apply sysctl
local swappiness
swappiness=$(grep 'swappiness:' "$profile" | tail -1 | awk '{print $2}')
if [[ -n "$swappiness" && "$swappiness" != "null" ]]; then
printf ' swappiness → %s: ' "$swappiness"
br_ssh "$node" "sudo sysctl -w vm.swappiness=$swappiness >/dev/null 2>&1" && \
printf '%bok%b\n' "$GREEN" "$RESET" || printf '%bfail%b\n' "$RED" "$RESET"
fi
printf '%bProfile %s applied to %s%b\n' "$GREEN" "$profile_name" "$node" "$RESET"
}
cmd_scan() {
printf '%bScanning fleet hardware...%b\n\n' "$AMBER" "$RESET"
# Detect local first
detect_local
echo ""
# Then all Pi nodes
for node in "${PI_NODES[@]}"; do
cmd_detect "$node" 2>/dev/null || true
echo ""
done
printf '%bFleet scan complete. Profiles in %s/detected/%b\n' "$GREEN" "$HW_DIR" "$RESET"
}
cmd_info() {
local node="$1"
local detected="$HW_DIR/detected/${node}.yaml"
if [[ ! -f "$detected" ]]; then
echo "No hardware data for '$node'. Run: br-hw detect $node"
return 1
fi
cat "$detected"
}
# Main
case "${1:-}" in
detect) cmd_detect "${2:-}" ;;
list) cmd_list ;;
scan) cmd_scan ;;
add) cmd_add "${2:?profile name required}" ;;
apply) cmd_apply "${2:?profile name required}" "${3:-}" ;;
info) cmd_info "${2:?node name required}" ;;
-h|--help|help|"") usage ;;
*) echo "Unknown: $1"; usage; exit 1 ;;
esac