Add per-enclosure temperature metrics + Home Assistant MQTT publisher
- enclosure: fetch SES element descriptor page (0x07) and label temp/fan/ psu/voltage elements with human-readable names - temps service + /api/temps: per-enclosure hotspot (max across SES sensors and housed drive temps) plus named SES sensor list - mqtt_publisher: paho-mqtt with HA discovery (device per enclosure, hotspot + per-sensor entities), LWT availability, opt-in via MQTT_HOST - secrets: OpenBao KV v2 reader; MQTT creds sourced from secret/home_assistant with env fallback - compose/requirements/README updated
This commit is contained in:
58
services/secrets.py
Normal file
58
services/secrets.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Minimal OpenBao (Vault) KV v2 reader for runtime secret injection.
|
||||
|
||||
Mirrors the homelab pattern used by other non-k8s services: read
|
||||
``<addr>/v1/<mount>/data/<path>`` with an ``X-Vault-Token``. Stdlib only —
|
||||
no extra dependency. Token comes from ``OPENBAO_TOKEN`` or, preferably, a
|
||||
mounted token file via ``OPENBAO_TOKEN_FILE`` (Docker/host secret).
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_ADDR = "https://vault.adamksmith.xyz"
|
||||
DEFAULT_MOUNT = "secret"
|
||||
|
||||
|
||||
def _resolve_token() -> str | None:
|
||||
token = os.environ.get("OPENBAO_TOKEN")
|
||||
if token:
|
||||
return token.strip()
|
||||
token_file = os.environ.get("OPENBAO_TOKEN_FILE")
|
||||
if token_file:
|
||||
try:
|
||||
return open(token_file).read().strip()
|
||||
except OSError as e:
|
||||
logger.warning("Could not read OPENBAO_TOKEN_FILE %s: %s", token_file, e)
|
||||
return None
|
||||
|
||||
|
||||
def read_kv2(path: str) -> dict | None:
|
||||
"""Read a KV v2 secret. ``path`` is relative to the mount (e.g.
|
||||
"jbod-monitor/mqtt"). Returns the inner data dict, or None on failure.
|
||||
"""
|
||||
token = _resolve_token()
|
||||
if not token:
|
||||
logger.warning("OpenBao read requested for %s but no token configured", path)
|
||||
return None
|
||||
|
||||
addr = os.environ.get("OPENBAO_ADDR", DEFAULT_ADDR).rstrip("/")
|
||||
mount = os.environ.get("OPENBAO_MOUNT", DEFAULT_MOUNT).strip("/")
|
||||
url = f"{addr}/v1/{mount}/data/{path.strip('/')}"
|
||||
|
||||
req = urllib.request.Request(url, headers={"X-Vault-Token": token})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
body = json.loads(resp.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
logger.warning("OpenBao read %s failed: HTTP %s", path, e.code)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("OpenBao read %s failed: %s", path, e)
|
||||
return None
|
||||
|
||||
# KV v2 wraps the secret in data.data.
|
||||
return body.get("data", {}).get("data")
|
||||
Reference in New Issue
Block a user