"""Seeds writable runtime copies of the read-only config templates in the image. Two of this agent's config files (audio-config.json, rdp-vnc.json) are rewritten at runtime — the HA "Audio output" select has to survive a reboot, and remote-desktop targets have to be editable without rebuilding an ISO. Neither can live where the build put them: the live image's /etc is inside a squashfs. 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("THINCLIENT_TEMPLATE_DIR", "/etc/thinclient-agent") STATE_DIR = os.environ.get("THINCLIENT_STATE_DIR", "/var/lib/thinclient-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