#!/bin/bash # Deploy lm-sensors extend (native sensors + TEMPer) + snmpd on a host [#341/#439] # # Run ON the target host via the remote.sh chokepoint (bash -s). Copy the # wrapper over first (vm-copy / prox-copy). Idempotent: safe to re-run. # # Native lm-sensors (CPU/board temps, fans) is installed on every target. # The TEMPer USB layer (venv + temperusb + udev rule) is installed ONLY when # a 0c45:7401 probe is attached. # # Usage (from repo root): # VM_IP=stlpc-artroom VM_USER=root bash tests/remote.sh vm-copy dcinfra/sensors/temper/lmsensors-extend.sh /tmp/lmsensors-extend.sh # VM_IP=stlpc-artroom VM_USER=root bash tests/remote.sh vm-file dcinfra/sensors/temper/deploy.sh # PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-copy dcinfra/sensors/temper/lmsensors-extend.sh /tmp/lmsensors-extend.sh # PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file dcinfra/sensors/temper/deploy.sh # # snmpd is a targeted exception: sensor hosts + tsys-* Proxmox hosts # (founder-directed 2026-08-27), not fleet-wide. set -euo pipefail COMMUNITY="${SNMP_COMMUNITY:-kn3lmgmt}" VENV_DIR="${VENV_DIR:-/opt/temper-venv}" WRAP_SRC="${WRAP_SRC:-/tmp/lmsensors-extend.sh}" WRAP_DST="/usr/local/bin/lmsensors-extend" WRAP_DST_OLD="/usr/local/bin/temper-lmsensors" SNMPD_CONF="/etc/snmp/snmpd.conf" UDEV_RULE="/etc/udev/rules.d/99-temper.rules" MARK_BEGIN="# lmsensors-extend managed block begin" MARK_END="# lmsensors-extend managed block end" OLD_MARK_BEGIN="# temper-lmsensors managed block begin" OLD_MARK_END="# temper-lmsensors managed block end" # Source allowlist (founder ruling 2026-08-27: bind Tailscale-only where # possible; HA is a Tailscale APP connector so its polls source from its LAN # IP and must be ACL'd explicitly — no broad subnet grants). # 192.168.3.176 tsys-librenms LAN (poller) # 100.86.204.77 tsys-librenms Tailscale # 192.168.3.12 pfv-bms / Home Assistant LAN (app connector source) # 100.67.108.125 pfv-bms Tailscale # 100.101.187.119 admin workstation Tailscale # 152.53.37.179 + 2a0a:4cc0:2000:686::a496:3bff:fe35:610a Cloudron VPS # (future Uptime Kuma SNMP polling) SNMP_SOURCES="${SNMP_SOURCES:-192.168.3.176 192.168.3.252 192.168.3.253 100.70.181.72 100.71.171.20 100.86.204.77 192.168.3.12 100.67.108.125 100.101.187.119 152.53.37.179 2a0a:4cc0:2000:686:a496:3bff:fe35:610a}" log() { echo "[deploy] $*"; } has_probe() { command -v lsusb >/dev/null 2>&1 && lsusb | grep -qi '0c45:7401' } host_lan_ip() { # first 192.168.* address (mgmt/user LAN; excludes 127/10.100 storage/dockers) ip -4 addr show 2>/dev/null | grep -oP 'inet \K192\.168\.[0-9.]+' | head -1 } host_ts_ip() { command -v tailscale >/dev/null 2>&1 && tailscale ip -4 2>/dev/null | head -1 } [[ $EUID -eq 0 ]] || { echo "must run as root" >&2; exit 1; } [[ -f "$WRAP_SRC" ]] || { echo "wrapper not found at $WRAP_SRC (copy it first)" >&2; exit 1; } # --- native lm-sensors layer (all hosts) --- if ! dpkg -s lm-sensors >/dev/null 2>&1; then log "installing lm-sensors" DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq lm-sensors fi if [[ -z "$(sensors 2>/dev/null)" ]]; then log "no sensor readings yet; trying coretemp/k10temp modules" modprobe coretemp 2>/dev/null || true modprobe k10temp 2>/dev/null || true fi log "native sensors: $(sensors 2>/dev/null | grep -c '°C') temp readings" # --- TEMPer USB layer (probe-conditional) --- if has_probe; then log "TEMPer probe present: installing venv layer" if ! dpkg -s python3-venv >/dev/null 2>&1; then log "installing python3-venv" DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-venv fi if [[ ! -x "$VENV_DIR/bin/pip" ]]; then [[ -d "$VENV_DIR" ]] && { log "removing broken venv $VENV_DIR"; rm -rf "$VENV_DIR"; } log "creating venv $VENV_DIR" python3 -m venv "$VENV_DIR" fi log "ensuring temperusb in venv" "$VENV_DIR/bin/pip" install --quiet --disable-pip-version-check temperusb # TEMPer USB dongle must be readable by the unprivileged snmpd user # (extend runs as Debian-snmp; pyusb needs the raw usb device node). log "installing udev rule for 0c45:7401" cat >"$UDEV_RULE" <<'EOF' SUBSYSTEM=="usb", ATTRS{idVendor}=="0c45", ATTRS{idProduct}=="7401", MODE="0666" EOF udevadm control --reload-rules udevadm trigger else log "no TEMPer probe: skipping venv/udev layer" fi log "installing wrapper to $WRAP_DST" install -m 0755 "$WRAP_SRC" "$WRAP_DST" rm -f "$WRAP_DST_OLD" if ! dpkg -s snmpd >/dev/null 2>&1 || ! command -v snmpget >/dev/null 2>&1; then log "installing snmpd + snmp client" DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq snmpd snmp fi log "configuring snmpd (community=$COMMUNITY, scoped sources)" LAN_IP="$(host_lan_ip)" TS_IP="$(host_ts_ip)" AGENT_ADDRS="" [[ -n "$LAN_IP" ]] && AGENT_ADDRS="udp:${LAN_IP}:161" [[ -n "$TS_IP" ]] && AGENT_ADDRS="${AGENT_ADDRS:+$AGENT_ADDRS,}udp:${TS_IP}:161" [[ -n "$AGENT_ADDRS" ]] || { echo "no LAN/Tailscale address found; refusing to bind 0.0.0.0" >&2; exit 1; } sed -i "/^$OLD_MARK_BEGIN\$/,/^$OLD_MARK_END\$/d" "$SNMPD_CONF" sed -i "/^$MARK_BEGIN\$/,/^$MARK_END\$/d" "$SNMPD_CONF" sed -i 's/^agentaddress/#agentaddress/' "$SNMPD_CONF" { echo "$MARK_BEGIN" echo "agentaddress $AGENT_ADDRS" for src in $SNMP_SOURCES; do if [[ "$src" == *:* ]]; then echo "rocommunity6 $COMMUNITY $src"; else echo "rocommunity $COMMUNITY $src"; fi done echo "extend lmsensors $WRAP_DST" echo "extend lmsensors_n $WRAP_DST -n" echo "$MARK_END" } >>"$SNMPD_CONF" log "agentaddress: $AGENT_ADDRS" log "restarting snmpd" systemctl enable --now snmpd >/dev/null systemctl restart snmpd log "wrapper output:" "$WRAP_DST" log "snmp extend self-test (expect lm-sensors block below):" sleep 1 snmpget -v2c -c "$COMMUNITY" -Ovq localhost \ .1.3.6.1.4.1.8072.1.3.2.3.1.2.9.108.109.115.101.110.115.111.114.115 2>/dev/null || \ log "WARN: snmpget self-test failed (check snmpd logs)" log "done"