move_detect/tests/test_deploy_artifacts.py
유창욱 2ffa74b639 feat(deploy): air-gapped offline bundle + systemd unit for edge daemon
Offline-installable bundle (wheelhouse + model + source) built on a machine with
internet and installed on the air-gapped Pi with --no-index (zero fetches), plus a
systemd unit (auto-restart, device access, hardening) and env template. Runs the
package from source via python -m edge (no offline build backend). Drift-guard test
keeps the env template in sync with what the daemon reads and rejects inline comments
that systemd EnvironmentFile= would fold into values.
2026-07-12 17:51:45 +09:00

57 lines
2.2 KiB
Python

"""Guard: the systemd unit + env template must stay in sync with what the daemon
actually reads. If someone renames an env var in edge/config.py or edge/daemon.py
(or edits the unit), this fails instead of shipping a broken bundle.
"""
import os
DEPLOY = os.path.join(os.path.dirname(__file__), "..", "deploy", "systemd")
ENV_EXAMPLE = os.path.join(DEPLOY, "move-detect-edge.env.example")
UNIT = os.path.join(DEPLOY, "move-detect-edge.service")
# Keys the daemon consumes (edge/config.py:load_config + edge/daemon.py:main).
REQUIRED_ENV = {
"DEVICE_ID", "DEVICE_SECRET", "CAFE24_URL", "CAMERA_ID",
"MOVENET_ONNX", "UVC_DEVICE", "TARGET_FPS", "QUEUE_DB",
}
def _env_keys(text):
keys = set()
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
keys.add(line.split("=", 1)[0].strip())
return keys
def test_env_example_lists_every_required_key():
with open(ENV_EXAMPLE, encoding="utf-8") as f:
keys = _env_keys(f.read())
missing = REQUIRED_ENV - keys
assert not missing, f"env template missing required keys: {sorted(missing)}"
def test_env_example_values_have_no_inline_comments():
# systemd EnvironmentFile= does NOT strip inline comments — a trailing "# ..." on
# a KEY=value line becomes part of the value and breaks int()/float()/path/secret.
with open(ENV_EXAMPLE, encoding="utf-8") as f:
offenders = []
for line in f.read().splitlines():
s = line.strip()
if not s or s.startswith("#") or "=" not in s:
continue
value = s.split("=", 1)[1]
if "#" in value:
offenders.append(s)
assert not offenders, f"inline comments in env values break systemd: {offenders}"
def test_systemd_unit_wiring():
with open(UNIT, encoding="utf-8") as f:
unit = f.read()
assert "EnvironmentFile=/etc/move-detect/edge.env" in unit
assert "PYTHONPATH=/opt/move-detect/src" in unit
assert "python -m edge" in unit # runs the package entrypoint
assert "Restart=always" in unit # unattended resilience
assert "WantedBy=multi-user.target" in unit