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
This commit is contained in:
369
bin/br-hw
Executable file
369
bin/br-hw
Executable file
@@ -0,0 +1,369 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user