clawdie-iso/live/operator-session/clawdie-hw-probe
Sam & Claude 7300fec1e2 0.12.0: hw-probe + model fixes + mother MCP infra
Combined from three feature branches:
- feature/hw-probe-agent-bootstrap: JSON hardware probe (clawdie-hw-probe),
  remove desktop icon, update START-HERE.txt
- chore/0.12.0-model-fix-bump: deepseek-v4-pro model names, version 0.12.0
- feature/mother-mcp-infra: build-colibri.sh MCP tool, colibri-mcp-ssh wrapper
2026-06-23 10:49:38 +02:00

288 lines
8.6 KiB
Bash
Executable file

#!/bin/sh
# clawdie-hw-probe — single-shot JSON hardware probe
#
# Outputs one JSON object to stdout. All messages/errors go to stderr.
# Designed to run as root for full hardware access.
# Target: < 3 seconds on cold cache.
#
# Schema: clawdie.hw-probe.v1
set -u
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
########################################################################
# helpers
########################################################################
die() {
printf '{"error":"%s","schema_version":"clawdie.hw-probe.v1"}\n' "$(json_escape "$1")"
exit 1
}
json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g; s/\r//g; s/\t/\\t/g'
}
json_bool() {
if [ "$1" = "yes" ] || [ "$1" = "YES" ] || [ "$1" = "true" ] || [ "$1" = "TRUE" ] || [ "$1" = "1" ]; then
printf 'true'
else
printf 'false'
fi
}
########################################################################
# field collectors — each function prints to a temp file or variable
########################################################################
collect_hostname() {
hostname 2>/dev/null || echo "unknown"
}
collect_freebsd_version() {
# kernel, userland, runtime
_k="$(freebsd-version -k 2>/dev/null || echo "unknown")"
_u="$(freebsd-version -u 2>/dev/null || echo "unknown")"
_r="$(freebsd-version -r 2>/dev/null || echo "unknown")"
printf '{"kernel":"%s","userland":"%s","runtime":"%s"}' \
"$(json_escape "$_k")" \
"$(json_escape "$_u")" \
"$(json_escape "$_r")"
}
collect_ram_gb() {
_bytes="$(sysctl -n hw.physmem 2>/dev/null || echo 0)"
# Round to nearest GB: add 0.5 GB then truncate
_half_gb=536870912 # 2^29
_gb=$(( ( _bytes + _half_gb ) / 1073741824 ))
printf '%d' "$_gb"
}
collect_cpu_model() {
sysctl -n hw.model 2>/dev/null || echo "unknown"
}
collect_cpu_cores() {
sysctl -n hw.ncpu 2>/dev/null || echo "0"
}
collect_gpu() {
# Extract VGA/display devices from pciconf -lv
# Output: JSON array of {vendor, device, chip}
_found=0
printf '['
pciconf -lv 2>/dev/null | awk '
BEGIN { in_block=0; vendor=""; device=""; chip=""; first=1 }
/^[a-z]/ {
if (in_block && device ~ /VGA|display/) {
if (!first) printf ",";
gsub(/"/, "\\\"", vendor);
gsub(/"/, "\\\"", device);
gsub(/"/, "\\\"", chip);
printf "{\"vendor\":\"%s\",\"device\":\"%s\",\"chip\":\"%s\"}", vendor, device, chip;
first=0;
}
in_block=1; vendor=""; device=""; chip=$0;
sub(/@.*/, "", chip);
}
/vendor *=/ { vendor=$0; sub(/^[[:space:]]*vendor[[:space:]]*=[[:space:]]*/, "", vendor); sub(/'\''/, "", vendor); sub(/'\''$/, "", vendor) }
/device *=/ { device=$0; sub(/^[[:space:]]*device[[:space:]]*=[[:space:]]*/, "", device); sub(/'\''/, "", device); sub(/'\''$/, "", device) }
END {
if (in_block && device ~ /VGA|display/) {
if (!first) printf ",";
gsub(/"/, "\\\"", vendor);
gsub(/"/, "\\\"", device);
gsub(/"/, "\\\"", chip);
printf "{\"vendor\":\"%s\",\"device\":\"%s\",\"chip\":\"%s\"}", vendor, device, chip;
}
}
'
printf ']'
}
collect_gpu_driver() {
# Check which KMS drivers are loaded
printf '['
_first=1
for _drv in amdgpu i915kms nvidia-modeset; do
if kldstat -q -n "$_drv" 2>/dev/null; then
if [ "$_first" -ne 1 ]; then printf ','; fi
printf '"%s"' "$_drv"
_first=0
fi
done
printf ']'
}
collect_disks() {
# Disk names + sizes
printf '{'
_disks="$(sysctl -n kern.disks 2>/dev/null || true)"
_first=1
for _disk in $_disks; do
_size=""
# Try geom disk list first (fast, no device open)
if command -v geom >/dev/null 2>&1; then
_size="$(geom disk list "$_disk" 2>/dev/null | awk '/Mediasize:/ {print $2}' | head -1)"
fi
# Fallback to camcontrol
if [ -z "$_size" ] && command -v camcontrol >/dev/null 2>&1; then
_size="$(camcontrol identify "/dev/${_disk}" 2>/dev/null | awk -F'[<>]' '/LBA48 supported|LBA supported/{print $2}' | head -1)"
fi
# Fallback to diskinfo
if [ -z "$_size" ] && command -v diskinfo >/dev/null 2>&1; then
_size="$(diskinfo "/dev/${_disk}" 2>/dev/null | awk '{print $3}' | head -1)"
fi
if [ "$_first" -ne 1 ]; then printf ','; fi
printf '"%s":%s' "$(json_escape "$_disk")" "${_size:-0}"
_first=0
done
printf '}'
}
collect_network_interfaces() {
# Simpler, reliable version that doesn't rely on subshell variable leakage
_ifaces="$(ifconfig -l 2>/dev/null)"
printf '{'
_first=1
for _iface in $_ifaces; do
_mac="$(ifconfig "$_iface" 2>/dev/null | awk '/ether/{print $2}' | head -1)"
[ -z "$_mac" ] && continue
if [ "$_first" -ne 1 ]; then printf ','; fi
printf '"%s":"%s"' "$(json_escape "$_iface")" "$(json_escape "$_mac")"
_first=0
done
printf '}'
}
collect_wifi() {
_wlan="$(sysctl -n net.wlan.devices 2>/dev/null || true)"
if [ -n "$_wlan" ]; then
printf '['
_first=1
for _dev in $_wlan; do
if [ "$_first" -ne 1 ]; then printf ','; fi
printf '"%s"' "$(json_escape "$_dev")"
_first=0
done
printf ']'
else
printf '[]'
fi
}
collect_usb_devices() {
if command -v usbconfig >/dev/null 2>&1; then
printf '%d' "$(usbconfig 2>/dev/null | grep -c '^ugen' || echo 0)"
else
printf '0'
fi
}
collect_xorg_running() {
if pgrep -x Xorg >/dev/null 2>&1; then
printf 'true'
else
printf 'false'
fi
}
collect_vulkan_support() {
if command -v vulkaninfo >/dev/null 2>&1 && \
vulkaninfo --summary >/dev/null 2>&1; then
printf 'true'
else
printf 'false'
fi
}
collect_boot_type() {
if [ -e /dev/da0 ]; then
printf '"usb"'
elif [ -e /dev/nda0 ] || [ -e /dev/nvd0 ]; then
printf '"nvme"'
elif [ -e /dev/ada0 ]; then
printf '"sata"'
else
printf '"unknown"'
fi
}
collect_colibri_running() {
if command -v service >/dev/null 2>&1; then
# service status can hang on rc.d scripts; enforce a 1s deadline
_status="$(timeout 1 service colibri_daemon status 2>/dev/null || echo "not_running")"
case "$_status" in
*"is running"*|*"Running"*)
printf 'true'
;;
*)
printf 'false'
;;
esac
else
printf 'false'
fi
}
collect_zfs_pools() {
if command -v zpool >/dev/null 2>&1; then
_pools="$(zpool list -H -o name 2>/dev/null || true)"
if [ -n "$_pools" ]; then
printf '['
_first=1
for _pool in $_pools; do
if [ "$_first" -ne 1 ]; then printf ','; fi
printf '"%s"' "$(json_escape "$_pool")"
_first=0
done
printf ']'
return
fi
fi
printf '[]'
}
########################################################################
# assemble JSON
########################################################################
_hostname="$(collect_hostname)"
_freebsd_ver="$(collect_freebsd_version)"
_ram_gb="$(collect_ram_gb)"
_cpu_model="$(collect_cpu_model)"
_cpu_cores="$(collect_cpu_cores)"
_gpu="$(collect_gpu)"
_gpu_driver="$(collect_gpu_driver)"
_disks="$(collect_disks)"
_net="$(collect_network_interfaces)"
_wifi="$(collect_wifi)"
_usb="$(collect_usb_devices)"
_xorg="$(collect_xorg_running)"
_vulkan="$(collect_vulkan_support)"
_boot_type="$(collect_boot_type)"
_colibri="$(collect_colibri_running)"
_zfs_pools="$(collect_zfs_pools)"
printf '{\n'
printf ' "schema_version":"clawdie.hw-probe.v1",\n'
printf ' "hostname":"%s",\n' "$(json_escape "$_hostname")"
printf ' "freebsd_version":%s,\n' "$_freebsd_ver"
printf ' "ram_gb":%s,\n' "$_ram_gb"
printf ' "cpu_model":"%s",\n' "$(json_escape "$_cpu_model")"
printf ' "cpu_cores":%s,\n' "$_cpu_cores"
printf ' "gpu":%s,\n' "$_gpu"
printf ' "gpu_driver":%s,\n' "$_gpu_driver"
printf ' "disks":%s,\n' "$_disks"
printf ' "network_interfaces":%s,\n' "$_net"
printf ' "wifi":%s,\n' "$_wifi"
printf ' "usb_devices":%s,\n' "$_usb"
printf ' "xorg_running":%s,\n' "$_xorg"
printf ' "vulkan_support":%s,\n' "$_vulkan"
printf ' "boot_type":%s,\n' "$_boot_type"
printf ' "colibri_running":%s,\n' "$_colibri"
printf ' "zfs_pools":%s\n' "$_zfs_pools"
printf '}\n'
exit 0