SmartestHome/hosts/steam-tv-box/agent/steamtv_agent/runtime_state.py

76 lines
2.9 KiB
Python

"""Seeds writable runtime copies of the read-only config templates in the image.
One of this agent's config files (audio-config.json) is rewritten at runtime — the HA
"Audio output" select has to survive a reboot, and it cannot live where the build put
it, because the live image's /etc is inside a squashfs. (The thin client's copy of this
module also covers rdp-vnc.json; this box has no outbound remote-desktop client, so
audio is the only file here. The module is kept whole rather than trimmed so the two
stay comparable.)
So the same split the wayvnc password already uses applies here — a committed template
that the build bakes in read-only, plus a real file created on the booted machine that
is never committed. The difference is that wayvnc's real file is written by a human and
fails closed if they forget, whereas these two are seeded automatically from the
template, because "no audio-output preference yet" is a perfectly safe state and there
is nothing to fail closed about.
"""
from __future__ import annotations
import json
import logging
import os
import shutil
import tempfile
log = logging.getLogger(__name__)
TEMPLATE_DIR = os.environ.get("STEAMTV_TEMPLATE_DIR", "/etc/steamtv-agent")
STATE_DIR = os.environ.get("STEAMTV_STATE_DIR", "/var/lib/steamtv-agent")
def ensure_runtime_copy(filename: str) -> str:
"""Return the writable path for `filename`, seeding it from the template if new."""
runtime_path = os.path.join(STATE_DIR, filename)
if os.path.exists(runtime_path):
return runtime_path
template_path = os.path.join(TEMPLATE_DIR, filename)
try:
os.makedirs(STATE_DIR, exist_ok=True)
shutil.copyfile(template_path, runtime_path)
log.info("seeded %s from %s", runtime_path, template_path)
except OSError as exc:
log.warning("could not seed %s from %s: %s", runtime_path, template_path, exc)
return runtime_path
def load_json(path: str) -> dict:
try:
with open(path, encoding="utf-8") as handle:
data = json.load(handle)
except (OSError, ValueError) as exc:
log.warning("could not read %s (%s); using defaults", path, exc)
return {}
return data if isinstance(data, dict) else {}
def save_json(path: str, data: dict) -> bool:
"""Write atomically — a half-written config on a power cut would be worse than a
stale one, since these files are read unattended at boot."""
directory = os.path.dirname(path) or "."
try:
os.makedirs(directory, exist_ok=True)
with tempfile.NamedTemporaryFile(
"w", encoding="utf-8", dir=directory, delete=False
) as handle:
json.dump(data, handle, indent=2)
handle.write("\n")
temp_path = handle.name
os.replace(temp_path, path)
except OSError as exc:
log.warning("could not write %s: %s", path, exc)
return False
return True