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
288 lines
8.6 KiB
Bash
Executable file
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
|