Set up centralized PDU management for a Cyclades AlterPath PM10i (10
controllable AC outlets) connected to pfv-tsys1 via a Prolific USB-to-DB9
serial adapter. powermand is now listening on 0.0.0.0:10101, making the
PDU manageable over the network from any host on the tailnet.
Scripts (powerman/):
- discover.sh: gather USB adapter, powerman state, device definitions
- setup.sh: idempotent setup — udev rule (stable symlink by serial number),
powerman.conf with 10 outlet nodes, fix powermand dialout group, restart
service. Overridable via env vars for other hosts/PDU types
- test-pdu.sh: validate control by cycling outlet 10 off then on (8/8 pass)
- status.sh: quick PDU status check
Issues fixed during setup:
- Config pointed at /dev/ttyUSB0 but adapter is at /dev/ttyUSB1 (fixed
with udev symlink /dev/cyclades-pm10 pinned to adapter serial)
- powermand (user:powerman) lacked dialout group membership to open the
serial device (fixed with usermod + udev GROUP="dialout")
Validation: outlet 10 turned off (confirmed), turned on (confirmed), then
cycled. All 10 outlets currently ON and manageable.
TODO tracked for Friday: rename outlets from generic (outlet-1..10) to
match physical devices, and change PDU admin password from factory default.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
168 lines
5.4 KiB
Bash
168 lines
5.4 KiB
Bash
#!/usr/bin/bash
|
|
#
|
|
# powerman/setup.sh — idempotent powerman setup for Cyclades PM10i PDU
|
|
#
|
|
# Creates a stable udev symlink for the USB-DB9 adapter, writes powerman.conf
|
|
# with 10 outlet nodes, and enables + starts powermand.
|
|
#
|
|
# This script is designed to be run ON the target host (pfv-tsys1) as root.
|
|
# It is idempotent: safe to run multiple times.
|
|
#
|
|
# Usage:
|
|
# PROX_HOST=pfv-tsys1 bash tests/remote.sh prox-file powerman/setup.sh
|
|
#
|
|
# Override defaults via environment variables:
|
|
# PDU_SERIAL — USB adapter serial (default: BJAAb144J07)
|
|
# PDU_VENDOR — USB vendor ID (default: 067b)
|
|
# PDU_DEV_NAME — udev symlink name (default: cyclades-pm10)
|
|
# PDU_BAUD — serial baud rate (default: 9600,8n1)
|
|
# PDU_TYPE — powerman spec type (default: pm10)
|
|
# PDU_OUTLETS — number of outlets (default: 10)
|
|
# PDU_LISTEN — powermand listen (default: 0.0.0.0:10101)
|
|
#
|
|
set -euo pipefail
|
|
|
|
# --- Config (overridable via env) ---
|
|
PDU_SERIAL="${PDU_SERIAL:-BJAAb144J07}"
|
|
PDU_VENDOR="${PDU_VENDOR:-067b}"
|
|
PDU_DEV_NAME="${PDU_DEV_NAME:-cyclades-pm10}"
|
|
PDU_BAUD="${PDU_BAUD:-9600,8n1}"
|
|
PDU_TYPE="${PDU_TYPE:-pm10}"
|
|
PDU_OUTLETS="${PDU_OUTLETS:-10}"
|
|
PDU_LISTEN="${PDU_LISTEN:-0.0.0.0:10101}"
|
|
|
|
UDEV_RULE="/etc/udev/rules.d/99-cyclades-pdu.rules"
|
|
POWERMAN_CONF="/etc/powerman/powerman.conf"
|
|
DEV_FILE="/etc/powerman/cyclades-pm10.dev"
|
|
|
|
echo "============================================"
|
|
echo " Powerman PDU Setup"
|
|
echo " Host: $(hostname)"
|
|
echo " PDU: Cyclades PM${PDU_OUTLETS}i"
|
|
echo " Adapter serial: $PDU_SERIAL"
|
|
echo " Device symlink: /dev/$PDU_DEV_NAME"
|
|
echo "============================================"
|
|
|
|
# --- 1. Ensure powerman is installed ---
|
|
echo ""
|
|
echo "--- [1/5] Checking powerman installation ---"
|
|
if ! dpkg -l powerman 2>/dev/null | grep -q '^ii'; then
|
|
echo " Installing powerman from Debian repo..."
|
|
apt-get update -qq && apt-get install -y -qq powerman
|
|
else
|
|
echo " Powerman already installed: $(dpkg -l powerman | awk '/^ii/{print $3}')"
|
|
fi
|
|
|
|
# --- 2. Create udev rule for stable device name ---
|
|
echo ""
|
|
echo "--- [2/5] Creating udev rule for USB-DB9 adapter ---"
|
|
cat > "$UDEV_RULE" <<UDEV
|
|
# Stable symlink for Cyclades PM10i PDU USB-DB9 adapter
|
|
# Generated by powerman/setup.sh
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="${PDU_VENDOR}", ATTRS{serial}=="${PDU_SERIAL}", GROUP="dialout", MODE="0660", SYMLINK+="${PDU_DEV_NAME}"
|
|
UDEV
|
|
echo " Written: $UDEV_RULE"
|
|
|
|
# Trigger udev to create the symlink now
|
|
udevadm control --reload-rules 2>/dev/null || true
|
|
udevadm trigger --subsystem-match=tty 2>/dev/null || true
|
|
sleep 1
|
|
|
|
if [ -e "/dev/${PDU_DEV_NAME}" ]; then
|
|
echo " Device symlink active: /dev/${PDU_DEV_NAME} -> $(readlink -f /dev/${PDU_DEV_NAME})"
|
|
else
|
|
echo " WARNING: /dev/${PDU_DEV_NAME} not found yet. Adapter may be unplugged."
|
|
echo " Falling back to /dev/ttyUSB* discovery..."
|
|
# Try to find any ttyUSB device as fallback
|
|
for tty in /dev/ttyUSB*; do
|
|
if [ -e "$tty" ]; then
|
|
echo " Found: $tty (using as fallback)"
|
|
PDU_DEV_NAME="$(basename "$tty")"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# --- 2b. Ensure powermand user can access the serial device ---
|
|
echo ""
|
|
echo "--- [2b/5] Fixing serial device permissions ---"
|
|
if id powerman >/dev/null 2>&1; then
|
|
if id powerman | grep -qv dialout; then
|
|
usermod -aG dialout powerman
|
|
echo " Added 'powerman' user to 'dialout' group"
|
|
else
|
|
echo " 'powerman' already in 'dialout' group"
|
|
fi
|
|
else
|
|
echo " (no powerman user — service may run as root)"
|
|
fi
|
|
|
|
# --- 3. Write powerman.conf ---
|
|
echo ""
|
|
echo "--- [3/5] Writing powerman.conf ---"
|
|
|
|
# Build node definitions
|
|
NODES=""
|
|
for i in $(seq 1 "$PDU_OUTLETS"); do
|
|
NODES+="node \"outlet-${i}\" \"${PDU_DEV_NAME}\" \"${i}\"\n"
|
|
done
|
|
|
|
cat > "$POWERMAN_CONF" <<PMCONF
|
|
# Powerman configuration for Cyclades PM${PDU_OUTLETS}i PDU
|
|
# Generated by powerman/setup.sh on $(date)
|
|
# Device: /dev/${PDU_DEV_NAME} (USB-DB9 adapter serial ${PDU_SERIAL})
|
|
|
|
# Listen on all interfaces so PDU is manageable over the network
|
|
listen "${PDU_LISTEN}"
|
|
|
|
# Device specification for Cyclades PM10
|
|
include "${DEV_FILE}"
|
|
|
|
# The PDU device (serial-attached)
|
|
device "${PDU_DEV_NAME}" "${PDU_TYPE}" "/dev/${PDU_DEV_NAME}" "${PDU_BAUD}"
|
|
|
|
# Outlet nodes (rename these to match attached devices when onsite)
|
|
$(printf '%b' "$NODES")
|
|
PMCONF
|
|
|
|
echo " Written: $POWERMAN_CONF"
|
|
echo " Nodes defined: outlet-1 through outlet-${PDU_OUTLETS}"
|
|
|
|
# --- 4. Restart powermand ---
|
|
echo ""
|
|
echo "--- [4/5] Restarting powermand ---"
|
|
systemctl enable powerman 2>/dev/null || true
|
|
systemctl restart powerman 2>/dev/null || true
|
|
sleep 2
|
|
|
|
if systemctl is-active --quiet powerman; then
|
|
echo " powermand is running."
|
|
else
|
|
echo " WARNING: powermand failed to start. Check journalctl -u powerman"
|
|
journalctl -u powerman --no-pager -n 20 2>/dev/null || true
|
|
fi
|
|
|
|
# --- 5. Verify ---
|
|
echo ""
|
|
echo "--- [5/5] Verification ---"
|
|
echo ""
|
|
echo " powerman -l (list all outlets):"
|
|
powerman -l 2>&1 || echo "(powerman -l failed)"
|
|
|
|
echo ""
|
|
echo " powerman -q (query status):"
|
|
powerman -q 2>&1 || echo "(powerman -q failed — PDU may need a moment)"
|
|
|
|
echo ""
|
|
echo "============================================"
|
|
echo " Setup complete."
|
|
echo ""
|
|
echo " Outlet names are generic (outlet-1 ... outlet-${PDU_OUTLETS})."
|
|
echo " Rename them in ${POWERMAN_CONF} when onsite to match attached devices."
|
|
echo ""
|
|
echo " Test: powerman -0 outlet-10 (off)"
|
|
echo " powerman -1 outlet-10 (on)"
|
|
echo " powerman -c outlet-10 (cycle)"
|
|
echo " powerman -q (status)"
|
|
echo "============================================"
|