8aad2ab43d
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
5.0 KiB
Python
122 lines
5.0 KiB
Python
"""Generate NUT (Network UPS Tools) configuration files.
|
|
|
|
Called by entrypoint.sh before the NUT daemons start. Reads config either from
|
|
environment variables (legacy NUT_MANAGED=1 path) or from a JSON file written
|
|
by the FabledScryer settings page (/data/nut_managed.json).
|
|
|
|
Environment variables (used when no --from-file is given):
|
|
NUT_DRIVER NUT driver (default: snmp-ups)
|
|
snmp-ups — any UPS with SNMP management card (APC, Eaton,
|
|
CyberPower, Tripp Lite, Liebert, ...)
|
|
netxml-ups — Eaton Network Management Card (XML/HTTP)
|
|
NUT_UPS_NAME Name exposed by upsd (default: ups)
|
|
Must match 'ups_name' in the UPS plugin settings.
|
|
NUT_UPS_HOST IP address or hostname of the UPS management card (required)
|
|
NUT_SNMP_COMMUNITY SNMP community string (default: public) [snmp-ups only]
|
|
NUT_SNMP_VERSION SNMP version: v1, v2c (default: v1) [snmp-ups only]
|
|
NUT_USERNAME Optional upsd username for authenticated access
|
|
NUT_PASSWORD Optional upsd password
|
|
|
|
JSON file format (--from-file path):
|
|
{
|
|
"ups_host": "192.168.1.x", // required
|
|
"driver": "snmp-ups",
|
|
"ups_name": "ups",
|
|
"snmp_community": "public",
|
|
"snmp_version": "v1",
|
|
"username": "",
|
|
"password": ""
|
|
}
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
NUT_CONF_DIR = Path("/etc/nut")
|
|
NUT_MANAGED_JSON = Path("/data/nut_managed.json")
|
|
|
|
|
|
def _env(key: str, default: str = "") -> str:
|
|
return os.environ.get(key, default).strip()
|
|
|
|
|
|
def write_configs(cfg: dict | None = None) -> None:
|
|
"""Write NUT config files. If cfg is given, use it; otherwise fall back to env vars."""
|
|
NUT_CONF_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
if cfg is not None:
|
|
driver = cfg.get("driver", "snmp-ups").strip()
|
|
ups_name = cfg.get("ups_name", "ups").strip()
|
|
ups_host = cfg.get("ups_host", "").strip()
|
|
community = cfg.get("snmp_community", "public").strip()
|
|
snmp_ver = cfg.get("snmp_version", "v1").strip()
|
|
username = cfg.get("username", "").strip()
|
|
password = cfg.get("password", "").strip()
|
|
else:
|
|
driver = _env("NUT_DRIVER", "snmp-ups")
|
|
ups_name = _env("NUT_UPS_NAME", "ups")
|
|
ups_host = _env("NUT_UPS_HOST")
|
|
community = _env("NUT_SNMP_COMMUNITY", "public")
|
|
snmp_ver = _env("NUT_SNMP_VERSION", "v1")
|
|
username = _env("NUT_USERNAME")
|
|
password = _env("NUT_PASSWORD")
|
|
|
|
if not ups_host:
|
|
print("[nut_setup] ERROR: NUT_UPS_HOST is required", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# ── nut.conf ───────────────────────────────────────────────────────────────
|
|
(NUT_CONF_DIR / "nut.conf").write_text("MODE=standalone\n")
|
|
|
|
# ── ups.conf ───────────────────────────────────────────────────────────────
|
|
ups_block = [f"[{ups_name}]"]
|
|
ups_block.append(f" driver = {driver}")
|
|
|
|
if driver == "netxml-ups":
|
|
# Eaton: port is a URL
|
|
ups_block.append(f" port = http://{ups_host}")
|
|
else:
|
|
# snmp-ups and others: port is the IP/hostname
|
|
ups_block.append(f" port = {ups_host}")
|
|
if driver == "snmp-ups":
|
|
ups_block.append(f" community = {community}")
|
|
ups_block.append(f" snmp_version = {snmp_ver}")
|
|
|
|
ups_block.append(f' desc = "Network UPS at {ups_host}"')
|
|
(NUT_CONF_DIR / "ups.conf").write_text("\n".join(ups_block) + "\n")
|
|
|
|
# ── upsd.conf ──────────────────────────────────────────────────────────────
|
|
(NUT_CONF_DIR / "upsd.conf").write_text("LISTEN 127.0.0.1 3493\n")
|
|
|
|
# ── upsd.users ─────────────────────────────────────────────────────────────
|
|
if username and password:
|
|
users_content = (
|
|
f"[{username}]\n"
|
|
f" password = {password}\n"
|
|
f" upsmon primary\n"
|
|
)
|
|
else:
|
|
users_content = "# No users configured — unauthenticated access\n"
|
|
users_path = NUT_CONF_DIR / "upsd.users"
|
|
users_path.write_text(users_content)
|
|
users_path.chmod(0o640)
|
|
|
|
print(
|
|
f"[nut_setup] Config written: driver={driver} host={ups_host} ups_name={ups_name}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
cfg = None
|
|
if "--from-file" in sys.argv:
|
|
idx = sys.argv.index("--from-file")
|
|
json_path = Path(sys.argv[idx + 1])
|
|
with json_path.open() as f:
|
|
cfg = json.load(f)
|
|
write_configs(cfg)
|
|
except Exception as exc:
|
|
print(f"[nut_setup] ERROR: {exc}", file=sys.stderr)
|
|
sys.exit(1)
|