SmartestHome/hosts/thin-client/agent/thinclient_agent/runtime_state.py

74 lines
2.7 KiB
Python

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