89 lines
4.1 KiB
Python
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.7.4", "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)
|