74 lines
2.7 KiB
Python
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
|