Add D455 sensor host and shared Node/Core preparation surface
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
// An authenticated Node action may start only this fixed model job.
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (subject.user === "mission-core-node" && action.id === "org.freedesktop.systemd1.manage-units" && action.lookup("unit") === "mission-core-node-realsense-prepare.service" && action.lookup("verb") === "start") {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
# Reviewed D455 only. No firmware/DFU IDs, unrelated cameras or world-writable devices.
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="8086", ATTR{idProduct}=="0b5c", MODE="0660", GROUP="mission-core-sensors"
|
||||
SUBSYSTEM=="video4linux", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b5c", MODE="0660", GROUP="mission-core-sensors"
|
||||
SUBSYSTEM=="hidraw", ATTRS{idVendor}=="8086", ATTRS{idProduct}=="0b5c", MODE="0660", GROUP="mission-core-sensors"
|
||||
@@ -13,7 +13,7 @@ import tarfile
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VERSION = "0.5.1"
|
||||
VERSION = "0.6.0"
|
||||
BRAND_SHA256 = "8bfee8ca9f98e0db48d98aae3af4b32493b8593e18b064a0239d513d824182af"
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ def desktop_icon(brand):
|
||||
|
||||
def tarball(files):
|
||||
stream = io.BytesIO()
|
||||
with tarfile.open(fileobj=stream, mode="w", format=tarfile.USTAR_FORMAT) as archive:
|
||||
with tarfile.open(fileobj=stream, mode="w", format=tarfile.GNU_FORMAT) as archive:
|
||||
directories = {str(parent) for name, _, _ in files for parent in Path(name).parents if str(parent) != "."}
|
||||
for name in sorted(directories):
|
||||
item = tarfile.TarInfo(name + "/")
|
||||
@@ -92,6 +92,24 @@ Description: Mission Core onboard computer configuration
|
||||
]:
|
||||
files.append((path, (p / source).read_bytes(), mode))
|
||||
files.append(("usr/share/mission-core-node/environment-profile.json", (ROOT / "internal/node/environment-profile.json").read_bytes(), 0o644))
|
||||
for name in ("realsense_prepare.py",):
|
||||
files.append(("usr/lib/mission-core-node/" + name, (p / name).read_bytes(), 0o644))
|
||||
for name in ("mission-core-realsense.service", "mission-core-node-realsense-prepare.service"):
|
||||
files.append(("usr/lib/systemd/system/" + name, (p / name).read_bytes(), 0o644))
|
||||
files.append(("usr/share/polkit-1/rules.d/50-mission-core-device-prepare.rules", (p / "50-mission-core-device-prepare.rules").read_bytes(), 0o644))
|
||||
files.append(("usr/share/mission-core-node/realsense/70-mission-core-realsense.rules", (p / "70-mission-core-realsense.rules").read_bytes(), 0o644))
|
||||
bundle = json.loads((p / "realsense-bundle.json").read_text())
|
||||
files.append(("usr/share/mission-core-node/realsense/bundle.json", (p / "realsense-bundle.json").read_bytes(), 0o644))
|
||||
for item in bundle["wheels"]:
|
||||
data = (ROOT / "build/realsense-wheels" / item["name"]).read_bytes()
|
||||
if hashlib.sha256(data).hexdigest() != item["sha256"]:
|
||||
raise ValueError("Driver bundle hash mismatch")
|
||||
files.append(("usr/share/mission-core-node/realsense/" + item["name"], data, 0o644))
|
||||
for path in (ROOT / "sensors").glob("*.py"):
|
||||
files.append(("usr/lib/mission-core-node/sensors/" + path.name, path.read_bytes(), 0o644))
|
||||
sdk = ROOT.parents[1] / "packages/plugin-sdk/python/missioncore_plugin_sdk"
|
||||
for path in sdk.rglob("*.py"):
|
||||
files.append(("usr/lib/mission-core-node/sdk/missioncore_plugin_sdk/" + str(path.relative_to(sdk)), path.read_bytes(), 0o644))
|
||||
if (ROOT / "build/provenance.json").exists():
|
||||
files.append(("usr/share/doc/mission-core-node/provenance.json", (ROOT / "build/provenance.json").read_bytes(), 0o644))
|
||||
archive = b"!<arch>\n" + ar_member("debian-binary", b"2.0\n") + ar_member("control.tar.gz", tarball(controls)) + ar_member("data.tar.gz", tarball(files))
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Engineering build input, never run on an operator board. Exact PyPI hashes only."""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
manifest = json.loads((root / "packaging/realsense-bundle.json").read_text())
|
||||
output = root / "build/realsense-wheels"
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
for item in manifest["wheels"]:
|
||||
target = output / item["name"]
|
||||
if target.exists() and hashlib.sha256(target.read_bytes()).hexdigest() == item["sha256"]:
|
||||
continue
|
||||
package, version = item["name"].split("-")[:2]
|
||||
with urlopen(f"https://pypi.org/pypi/{package}/{version}/json", timeout=30) as response:
|
||||
metadata = json.load(response)
|
||||
source = next(
|
||||
v
|
||||
for v in metadata["urls"]
|
||||
if v["filename"] == item["name"] and v["digests"]["sha256"] == item["sha256"]
|
||||
)
|
||||
if not source["url"].startswith("https://files.pythonhosted.org/"):
|
||||
raise ValueError("Unexpected package origin")
|
||||
with urlopen(source["url"], timeout=120) as response:
|
||||
data = response.read(item["bytes"] + 1)
|
||||
if len(data) != item["bytes"] or hashlib.sha256(data).hexdigest() != item["sha256"]:
|
||||
raise ValueError("Driver checksum mismatch")
|
||||
target.write_bytes(data)
|
||||
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Mission Core fixed RealSense model preparation
|
||||
After=systemd-udevd.service
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/python3 -I /usr/lib/mission-core-node/realsense_prepare.py
|
||||
TimeoutStartSec=300
|
||||
UMask=0022
|
||||
@@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=Mission Core isolated RealSense driver
|
||||
After=network.target
|
||||
ConditionPathExists=/var/lib/mission-core-node-drivers/active.path
|
||||
[Service]
|
||||
User=mission-core-sensors
|
||||
Group=mission-core-node
|
||||
SupplementaryGroups=mission-core-sensors
|
||||
ExecStart=/usr/bin/python3 -I /usr/lib/mission-core-node/sensors/bootstrap.py
|
||||
StateDirectory=mission-core-sensors
|
||||
StateDirectoryMode=0700
|
||||
RuntimeDirectory=mission-core-sensors
|
||||
RuntimeDirectoryMode=0750
|
||||
UMask=0077
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
NoNewPrivileges=yes
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
PrivateTmp=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectControlGroups=yes
|
||||
RestrictSUIDSGID=yes
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_NETLINK
|
||||
CapabilityBoundingSet=
|
||||
LockPersonality=yes
|
||||
TasksMax=128
|
||||
MemoryMax=900M
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -5,6 +5,9 @@ case "$1" in
|
||||
if ! getent passwd mission-core-node >/dev/null; then
|
||||
adduser --system --group --home /var/lib/mission-core-node --no-create-home --disabled-login mission-core-node
|
||||
fi
|
||||
if ! getent passwd mission-core-sensors >/dev/null; then
|
||||
adduser --system --group --home /var/lib/mission-core-sensors --no-create-home --disabled-login mission-core-sensors
|
||||
fi
|
||||
# Only bootstrap required to open the GUI. Operational configuration is a
|
||||
# versioned job started by «Настройка окружения → Сконфигурировать».
|
||||
if [ -d /run/systemd/system ]; then
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
set -eu
|
||||
if [ "$1" = install ] || [ "$1" = upgrade ]; then
|
||||
if [ -d /run/systemd/system ]; then
|
||||
mc_node_device_job=$(systemctl show --property=ActiveState --value mission-core-node-realsense-prepare.service 2>/dev/null || true)
|
||||
case "$mc_node_device_job" in
|
||||
active|activating) echo "Mission Core Node: дождитесь завершения подготовки устройства." >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
mc_node_environment_state=$(systemctl show --property=ActiveState --value mission-core-node-environment.service 2>/dev/null || true)
|
||||
case "$mc_node_environment_state" in
|
||||
active|activating)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
if [ -d /run/systemd/system ]; then
|
||||
mc_node_device_job=$(systemctl show --property=ActiveState --value mission-core-node-realsense-prepare.service 2>/dev/null || true)
|
||||
case "$mc_node_device_job" in
|
||||
active|activating) echo "Mission Core Node: дождитесь завершения подготовки устройства." >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
mc_node_environment_state=$(systemctl show --property=ActiveState --value mission-core-node-environment.service 2>/dev/null || true)
|
||||
case "$mc_node_environment_state" in
|
||||
active|activating)
|
||||
@@ -29,6 +34,8 @@ case "$1" in
|
||||
/usr/sbin/sshd -t
|
||||
systemctl try-reload-or-restart ssh.service
|
||||
fi
|
||||
systemctl stop mission-core-realsense.service
|
||||
systemctl disable mission-core-realsense.service || true
|
||||
systemctl stop mission-core-node.service
|
||||
systemctl disable mission-core-node.service
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"schema": "missioncore.node.driver-bundle/v1",
|
||||
"model_id": "realsense.d455",
|
||||
"revision": "c70509ac57b917dbdd5707a9",
|
||||
"python": "3.12",
|
||||
"platform": "linux-amd64",
|
||||
"wheels": [
|
||||
{
|
||||
"name": "aiohappyeyeballs-2.7.1-py3-none-any.whl",
|
||||
"sha256": "9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472",
|
||||
"bytes": 15038
|
||||
},
|
||||
{
|
||||
"name": "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||
"sha256": "fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545",
|
||||
"bytes": 1719929
|
||||
},
|
||||
{
|
||||
"name": "aioice-0.10.2-py3-none-any.whl",
|
||||
"sha256": "14911c15ab12d096dd14d372ebb4aecbb7420b52c9b76fdfcf54375dec17fcbf",
|
||||
"bytes": 24875
|
||||
},
|
||||
{
|
||||
"name": "aiortc-1.14.0-py3-none-any.whl",
|
||||
"sha256": "4b244d7e482f4e1f67e685b3468269628eca1ec91fa5b329ab517738cfca086e",
|
||||
"bytes": 93183
|
||||
},
|
||||
{
|
||||
"name": "aiosignal-1.4.0-py3-none-any.whl",
|
||||
"sha256": "053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e",
|
||||
"bytes": 7490
|
||||
},
|
||||
{
|
||||
"name": "annotated_types-0.8.0-py3-none-any.whl",
|
||||
"sha256": "f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0",
|
||||
"bytes": 13427
|
||||
},
|
||||
{
|
||||
"name": "attrs-26.1.0-py3-none-any.whl",
|
||||
"sha256": "c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309",
|
||||
"bytes": 67548
|
||||
},
|
||||
{
|
||||
"name": "av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl",
|
||||
"sha256": "7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2",
|
||||
"bytes": 41174337
|
||||
},
|
||||
{
|
||||
"name": "cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
|
||||
"sha256": "c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf",
|
||||
"bytes": 221822
|
||||
},
|
||||
{
|
||||
"name": "cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
|
||||
"sha256": "ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef",
|
||||
"bytes": 4712478
|
||||
},
|
||||
{
|
||||
"name": "dnspython-2.8.0-py3-none-any.whl",
|
||||
"sha256": "01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af",
|
||||
"bytes": 331094
|
||||
},
|
||||
{
|
||||
"name": "frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl",
|
||||
"sha256": "494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383",
|
||||
"bytes": 242411
|
||||
},
|
||||
{
|
||||
"name": "google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl",
|
||||
"sha256": "14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411",
|
||||
"bytes": 33364
|
||||
},
|
||||
{
|
||||
"name": "idna-3.19-py3-none-any.whl",
|
||||
"sha256": "815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4",
|
||||
"bytes": 68550
|
||||
},
|
||||
{
|
||||
"name": "ifaddr-0.2.0-py3-none-any.whl",
|
||||
"sha256": "085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748",
|
||||
"bytes": 12314
|
||||
},
|
||||
{
|
||||
"name": "multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
||||
"sha256": "bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961",
|
||||
"bytes": 256322
|
||||
},
|
||||
{
|
||||
"name": "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||
"sha256": "fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249",
|
||||
"bytes": 16527618
|
||||
},
|
||||
{
|
||||
"name": "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl",
|
||||
"sha256": "67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6",
|
||||
"bytes": 7644652
|
||||
},
|
||||
{
|
||||
"name": "propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
||||
"sha256": "6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476",
|
||||
"bytes": 61639
|
||||
},
|
||||
{
|
||||
"name": "pycparser-3.0-py3-none-any.whl",
|
||||
"sha256": "b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992",
|
||||
"bytes": 48172
|
||||
},
|
||||
{
|
||||
"name": "pydantic-2.11.7-py3-none-any.whl",
|
||||
"sha256": "dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b",
|
||||
"bytes": 444782
|
||||
},
|
||||
{
|
||||
"name": "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||
"sha256": "8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1",
|
||||
"bytes": 2002028
|
||||
},
|
||||
{
|
||||
"name": "pyee-14.0.0-py3-none-any.whl",
|
||||
"sha256": "3ac2d3229a9677f7de2c33d7f52fe25b638a46b19c413fea2edc8c6d0a644e4d",
|
||||
"bytes": 15553
|
||||
},
|
||||
{
|
||||
"name": "pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl",
|
||||
"sha256": "293c9f2ac21a2bd689c477603a1aa235d85cf252160e6715f0101e42a43cbedc",
|
||||
"bytes": 2434534
|
||||
},
|
||||
{
|
||||
"name": "pyopenssl-26.4.0-py3-none-any.whl",
|
||||
"sha256": "f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c",
|
||||
"bytes": 56026
|
||||
},
|
||||
{
|
||||
"name": "pyrealsense2-2.58.4.10922-cp312-cp312-manylinux1_x86_64.whl",
|
||||
"sha256": "1e83454cbaf9de50962d78ce3addb0b6a6d8d02259c1f0c64d4a0d527a02bf73",
|
||||
"bytes": 13094959
|
||||
},
|
||||
{
|
||||
"name": "typing_extensions-4.16.0-py3-none-any.whl",
|
||||
"sha256": "481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8",
|
||||
"bytes": 45571
|
||||
},
|
||||
{
|
||||
"name": "typing_inspection-0.4.4-py3-none-any.whl",
|
||||
"sha256": "65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147",
|
||||
"bytes": 14750
|
||||
},
|
||||
{
|
||||
"name": "yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
||||
"sha256": "f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9",
|
||||
"bytes": 109835
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
"""Fixed model job. No paths, packages, URLs or commands are accepted from clients."""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path, PurePosixPath
|
||||
|
||||
SHARE = Path("/usr/share/mission-core-node/realsense")
|
||||
ROOT = Path("/var/lib/mission-core-node-drivers")
|
||||
REPORT = ROOT / "preparation.json"
|
||||
STEPS = [
|
||||
("platform", "Проверка совместимости системы"),
|
||||
("payload", "Проверка встроенного драйвера"),
|
||||
("runtime", "Развёртывание драйвера"),
|
||||
("access", "Настройка доступа к камере"),
|
||||
("service", "Запуск службы камеры"),
|
||||
]
|
||||
|
||||
|
||||
def publish(value):
|
||||
ROOT.mkdir(mode=0o755, exist_ok=True)
|
||||
if ROOT.is_symlink() or ROOT.stat().st_uid != 0:
|
||||
raise RuntimeError("Небезопасный каталог драйверов")
|
||||
ROOT.chmod(0o755)
|
||||
tmp = ROOT / ".preparation.tmp"
|
||||
with tmp.open("w") as f:
|
||||
os.fchmod(f.fileno(), 0o644)
|
||||
json.dump(value, f, ensure_ascii=False)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
tmp.replace(REPORT)
|
||||
|
||||
|
||||
def run(*args):
|
||||
result = subprocess.run(args, capture_output=True, timeout=90)
|
||||
if result.returncode:
|
||||
raise RuntimeError("Системный этап не завершён. Повторите подготовку устройства.")
|
||||
|
||||
|
||||
def safe_members(archive):
|
||||
for info in archive.infolist():
|
||||
path = PurePosixPath(info.filename)
|
||||
if (
|
||||
path.is_absolute()
|
||||
or ".." in path.parts
|
||||
or (info.external_attr >> 16) & 0o170000 == 0o120000
|
||||
):
|
||||
raise RuntimeError("Недопустимое содержимое драйверного пакета")
|
||||
if ".data" in info.filename or info.filename.endswith(".pth"):
|
||||
raise RuntimeError("Пакет требует неподдерживаемый способ установки")
|
||||
yield info
|
||||
|
||||
|
||||
def prepare():
|
||||
manifest = json.loads((SHARE / "bundle.json").read_text())
|
||||
revision = manifest["revision"]
|
||||
if not revision.isalnum():
|
||||
raise RuntimeError("Некорректная версия драйвера")
|
||||
target = ROOT / revision
|
||||
state = {
|
||||
"schema": "missioncore.node.device-preparation/v1",
|
||||
"model_id": "realsense.d455",
|
||||
"revision": revision,
|
||||
"run_id": str(uuid.uuid4()),
|
||||
"started_at": time.time(),
|
||||
"state": "running",
|
||||
"steps": [{"id": k, "label": v, "state": "pending"} for k, v in STEPS],
|
||||
}
|
||||
publish(state)
|
||||
try:
|
||||
for step in state["steps"]:
|
||||
step["state"] = "running"
|
||||
state["updated_at"] = time.time()
|
||||
publish(state)
|
||||
if step["id"] == "platform":
|
||||
release = dict(
|
||||
line.split("=", 1)
|
||||
for line in Path("/etc/os-release").read_text().splitlines()
|
||||
if "=" in line
|
||||
)
|
||||
if (
|
||||
(release.get("ID", "").strip('"'), release.get("VERSION_ID", "").strip('"'))
|
||||
!= ("ubuntu", "24.04")
|
||||
or os.uname().machine != "x86_64"
|
||||
or sys.version_info[:2] != (3, 12)
|
||||
):
|
||||
raise RuntimeError("Встроенный драйвер несовместим с этой системой")
|
||||
elif step["id"] == "payload":
|
||||
for entry in manifest["wheels"]:
|
||||
path = SHARE / entry["name"]
|
||||
if (
|
||||
path.name != entry["name"]
|
||||
or path.is_symlink()
|
||||
or hashlib.sha256(path.read_bytes()).hexdigest() != entry["sha256"]
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Контрольная сумма драйвера не совпала. Переустановите пакет Node."
|
||||
)
|
||||
elif step["id"] == "runtime":
|
||||
if not target.exists():
|
||||
stage = ROOT / (revision + ".partial")
|
||||
if stage.exists():
|
||||
shutil.rmtree(stage)
|
||||
stage.mkdir(mode=0o755)
|
||||
for entry in manifest["wheels"]:
|
||||
with zipfile.ZipFile(SHARE / entry["name"]) as archive:
|
||||
archive.extractall(stage, members=safe_members(archive))
|
||||
for path in stage.rglob("*"):
|
||||
path.chmod(0o755 if path.is_dir() else 0o644)
|
||||
stage.rename(target)
|
||||
# Wheel RECORD content is checked again against the bundled wheel;
|
||||
# a previous successful report never substitutes for integrity.
|
||||
for entry in manifest["wheels"]:
|
||||
with zipfile.ZipFile(SHARE / entry["name"]) as archive:
|
||||
for info in safe_members(archive):
|
||||
if not info.is_dir() and (
|
||||
target / info.filename
|
||||
).read_bytes() != archive.read(info):
|
||||
raise RuntimeError(
|
||||
"Установленный драйвер изменён. "
|
||||
"Нужна переустановка пакета драйвера."
|
||||
)
|
||||
(ROOT / "active.path").write_text(str(target))
|
||||
(ROOT / "active.path").chmod(0o644)
|
||||
elif step["id"] == "access":
|
||||
source = SHARE / "70-mission-core-realsense.rules"
|
||||
dest = Path("/etc/udev/rules.d/70-mission-core-realsense.rules")
|
||||
if dest.is_symlink() or (
|
||||
dest.exists() and dest.read_bytes() != source.read_bytes()
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Правила доступа к камере изменены в системе. Чужая конфигурация сохранена."
|
||||
)
|
||||
dest.write_bytes(source.read_bytes())
|
||||
dest.chmod(0o644)
|
||||
run("/usr/bin/udevadm", "control", "--reload-rules")
|
||||
# Restrict trigger to the admitted product; no unrelated USB reset.
|
||||
run(
|
||||
"/usr/bin/udevadm",
|
||||
"trigger",
|
||||
"--action=change",
|
||||
"--subsystem-match=usb",
|
||||
"--attr-match=idVendor=8086",
|
||||
"--attr-match=idProduct=0b5c",
|
||||
)
|
||||
run(
|
||||
"/usr/bin/udevadm",
|
||||
"trigger",
|
||||
"--action=change",
|
||||
"--subsystem-match=video4linux",
|
||||
)
|
||||
run("/usr/bin/udevadm", "trigger", "--action=change", "--subsystem-match=hidraw")
|
||||
run("/usr/bin/udevadm", "settle", "--timeout=10")
|
||||
elif step["id"] == "service":
|
||||
run("/usr/bin/systemctl", "enable", "mission-core-realsense.service")
|
||||
# Never restart a running acquisition on repeated preparation.
|
||||
run("/usr/bin/systemctl", "start", "mission-core-realsense.service")
|
||||
run("/usr/bin/systemctl", "is-active", "--quiet", "mission-core-realsense.service")
|
||||
step["state"] = "complete"
|
||||
publish(state)
|
||||
state["state"] = "complete"
|
||||
except (
|
||||
OSError,
|
||||
ValueError,
|
||||
RuntimeError,
|
||||
subprocess.SubprocessError,
|
||||
zipfile.BadZipFile,
|
||||
) as error:
|
||||
step["state"] = "error"
|
||||
step["message"] = (
|
||||
str(error)[:300]
|
||||
if isinstance(error, RuntimeError)
|
||||
else "Не удалось подготовить драйвер. Повторите действие."
|
||||
)
|
||||
state["state"] = "error"
|
||||
for item in state["steps"]:
|
||||
if item["state"] == "pending":
|
||||
item["state"] = "blocked"
|
||||
state["updated_at"] = time.time()
|
||||
publish(state)
|
||||
return state["state"] == "complete"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.umask(0o022)
|
||||
if os.geteuid() != 0 or len(sys.argv) != 1:
|
||||
sys.exit(1)
|
||||
sys.exit(0 if prepare() else 1)
|
||||
@@ -0,0 +1,25 @@
|
||||
import io
|
||||
import unittest
|
||||
import zipfile
|
||||
from realsense_prepare import safe_members
|
||||
|
||||
|
||||
class BundleBoundaryTests(unittest.TestCase):
|
||||
def test_archives_cannot_escape_or_execute_import_hooks(self):
|
||||
for name in ('../../etc/shadow', '/root/escape', 'something.pth', 'sdk.data/purelib/code.py'):
|
||||
value = io.BytesIO()
|
||||
with zipfile.ZipFile(value, 'w') as archive:
|
||||
archive.writestr(name, b'payload')
|
||||
with zipfile.ZipFile(io.BytesIO(value.getvalue())) as archive:
|
||||
with self.assertRaises(RuntimeError):
|
||||
list(safe_members(archive))
|
||||
|
||||
def test_archive_symlink_is_rejected(self):
|
||||
value = io.BytesIO()
|
||||
with zipfile.ZipFile(value, 'w') as archive:
|
||||
info = zipfile.ZipInfo('device.py')
|
||||
info.external_attr = 0o120777 << 16
|
||||
archive.writestr(info, '/etc/shadow')
|
||||
with zipfile.ZipFile(io.BytesIO(value.getvalue())) as archive:
|
||||
with self.assertRaises(RuntimeError):
|
||||
list(safe_members(archive))
|
||||
Reference in New Issue
Block a user