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
370 lines
11 KiB
Bash
Executable File
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
|