"""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