Files
NODEDC_MISSION_CORE/plugins/vesc/packaging/prepare.py
T

89 lines
4.1 KiB
Python

"""Versioned, idempotent VESC profile; no packages downloaded and no motor I/O."""
import fcntl
import hashlib
import json
import os
from pathlib import Path
import platform
import subprocess
import sys
import time
import uuid
sys.path.insert(0, str(Path(__file__).resolve().parent))
from runtime.serial import discover
from runtime.service import atomic
STATE = Path("/var/lib/mission-core-node-profiles/vesc")
def prepare():
if os.geteuid() != 0 or sys.argv[1:]:
raise RuntimeError("Fixed system profile only")
release = platform.freedesktop_os_release()
if (release.get("ID"), release.get("VERSION_ID"), platform.machine()) != ("ubuntu", "24.04", "x86_64"):
raise RuntimeError("Ubuntu 24.04 amd64 required")
STATE.mkdir(mode=0o755, parents=True, exist_ok=True)
for directory in (STATE.parent, STATE):
info = directory.lstat()
if directory.is_symlink() or info.st_uid != 0 or info.st_mode & 0o022:
raise RuntimeError("Untrusted profile state")
lock = os.open(STATE / "prepare.lock", os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
report = {"schema": "missioncore.node.device-preparation/v1", "model_id": "vesc.controller",
"version": "0.6.3", "run_id": uuid.uuid4().hex, "started_at": time.time(),
"monotonic_started": time.monotonic(), "state": "running", "steps": []}
def publish():
atomic(STATE / "preparation.json", report)
os.chmod(STATE / "preparation.json", 0o644)
def run(name, label, args):
step = {"id": name, "label": label, "state": "running"}
report["steps"].append(step)
publish()
result = subprocess.run(args, capture_output=True, timeout=45,
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin", "LANG": "C.UTF-8"})
step["state"] = "complete" if result.returncode == 0 else "error"
publish()
if result.returncode:
raise RuntimeError("Не завершён этап: " + label)
try:
import pwd
try:
pwd.getpwnam("mission-core-vesc")
except KeyError:
run("account", "Подготовка доступа", ["/usr/sbin/adduser", "--system", "--group", "--home",
"/var/lib/mission-core-vesc", "--no-create-home", "--disabled-login", "mission-core-vesc"])
run("native", "Проверка VESC Tool", ["/usr/sbin/runuser", "-u", "mission-core-vesc", "--",
"/usr/bin/python3", "-I", "/usr/lib/mission-core-vesc/native_check.py"])
source = Path("/usr/share/mission-core-node/profiles/vesc/70-mission-core-vesc.rules")
rules = source.read_bytes()
report["udev_sha256"] = hashlib.sha256(rules).hexdigest()
target = Path("/etc/udev/rules.d/70-mission-core-vesc.rules")
if target.is_symlink():
raise RuntimeError("Untrusted udev destination")
target.write_bytes(rules)
target.chmod(0o644)
run("rules", "Настройка USB-доступа", ["/usr/bin/udevadm", "control", "--reload-rules"])
for i, device in enumerate(discover()):
run("usb" + str(i), "Применение USB-доступа", ["/usr/bin/udevadm", "trigger", "--action=change",
"/sys/class/tty/" + device.tty])
run("settle", "Проверка USB-доступа", ["/usr/bin/udevadm", "settle", "--timeout=10"])
run("enable", "Подготовка службы", ["/usr/bin/systemctl", "enable", "mission-core-vesc.service"])
run("runtime", "Запуск чтения контроллеров", ["/usr/bin/systemctl", "restart", "mission-core-vesc.service"])
report["state"] = "complete"
except (OSError, RuntimeError, subprocess.SubprocessError) as error:
report.update(state="error", message=str(error)[:300])
finally:
report["duration_seconds"] = time.monotonic() - report["monotonic_started"]
publish()
os.close(lock)
return report["state"] == "complete"
if __name__ == "__main__":
sys.exit(0 if prepare() else 1)