Add packaged Insta360 X4 integration and recover paired Node channels

Discover independent camera instances and prepare their versioned runtime
from Node or remote Core. Add isolated SDK workers, camera controls, raw
dual-fisheye WebRTC preview, and shared action/region loading states.

Recover existing Node bindings over known Tailscale addresses after a Core
LAN address change. Preserve identities and trust, pin both peers, migrate
endpoints with revision checks, and require real heartbeats for online status.
Fix the Python client certificate profile for Go X509 verification.

Pin Design Guideline 8c53f73 and retain installer/build/acceptance history.
Node 0.8.19 is installed; X4 0.1.3-3 is bundled but hardware activation is pending.

Validation: qualified DG/Node builds and Go race tests; 31 fleet tests;
Python-to-Go certificate interoperability and live tailnet recovery with five
fresh heartbeats; prior 38 X4 tests and bounded remote WebRTC acceptance.
Clean-OS, replug/power autonomy, local X4 video and long-run stability remain open.
This commit is contained in:
DCCONSTRUCTIONS
2026-09-10 09:21:24 +03:00
parent 54a85fdf50
commit a3c15e11e9
125 changed files with 11916 additions and 251 deletions
@@ -0,0 +1,5 @@
polkit.addRule(function(action, subject) {
if (subject.user === "mission-core-node" && action.id === "org.freedesktop.systemd1.manage-units" && action.lookup("unit") === "mission-core-node-insta360-x4-prepare.service" && action.lookup("verb") === "start") {
return polkit.Result.YES;
}
});
@@ -0,0 +1 @@
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="2e1a", ATTR{idProduct}=="0002", ATTR{product}=="Insta360 X4", GROUP="mission-core-x4-usb", MODE="0660"
@@ -0,0 +1,32 @@
"""Isolated, root-owned bootstrap; no dependency on system Python packages."""
import os
import sys
from pathlib import Path
CODE = Path(__file__).resolve().parent
sys.path.insert(0, str(CODE))
from layout import active, trusted # noqa: E402
def main():
trusted(CODE, True)
runtime = trusted(active(), True)
trusted(runtime / "python", True)
sys.path[:0] = [str(runtime / "python"), str(CODE)]
os.umask(0o007)
if sys.argv[1:] == ["broker"]:
from runtime.broker import main as run
run()
elif len(sys.argv) == 3 and sys.argv[1] == "worker":
from runtime.worker import main as run
os.chdir(runtime / "bin")
run(sys.argv[2], runtime)
else:
raise ValueError("Unsupported runtime entry point")
if __name__ == "__main__":
main()
+230
View File
@@ -0,0 +1,230 @@
"""Build the optional X4 package from pinned SDK, native output and wheel bytes."""
import argparse
import hashlib
import io
import json
import os
import sys
import zipfile
from pathlib import Path, PurePosixPath
ROOT = Path(__file__).resolve().parents[1]
REPOSITORY = ROOT.parents[1]
PACKAGING = ROOT / "packaging"
sys.path.insert(0, str(REPOSITORY / "scripts/packaging"))
from debian import package # noqa: E402
from fetch_sdk import verify # noqa: E402
VERSION = "0.1.3-3"
WHEELS = {
"aiohappyeyeballs",
"aiohttp",
"aioice",
"aiortc",
"aiosignal",
"annotated_types",
"attrs",
"av",
"cffi",
"cryptography",
"dnspython",
"frozenlist",
"google_crc32c",
"idna",
"ifaddr",
"multidict",
"propcache",
"pycparser",
"pydantic",
"pydantic_core",
"pyee",
"pylibsrtp",
"pyopenssl",
"typing_extensions",
"typing_inspection",
"yarl",
}
def digest(data):
return hashlib.sha256(data).hexdigest()
def archive_bytes(files):
stream = io.BytesIO()
with zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED) as archive:
for name, data in sorted(files.items()):
info = zipfile.ZipInfo(name, date_time=(2026, 9, 8, 0, 0, 0))
info.external_attr = 0o644 << 16
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, data)
return stream.getvalue()
def runtime_payload():
sdk = ROOT / "build/sdk"
lock = json.loads((PACKAGING / "sdk-lock.json").read_text())
verify(sdk, lock)
native = ROOT / "build/native"
provenance = json.loads((native / "provenance.json").read_text())
for name, expected in provenance["source"].items():
if digest((ROOT / "native" / name).read_bytes()) != expected:
raise ValueError("Native adapter sources changed since compilation")
if provenance["sdk_lock_sha256"] != digest((PACKAGING / "sdk-lock.json").read_bytes()):
raise ValueError("Native adapter SDK input changed")
binary = (native / "libmissioncore_x4.so").read_bytes()
if digest(binary) != provenance["binary"]["sha256"] or provenance["abi"] != 1:
raise ValueError("Native adapter provenance mismatch")
files = {"lib/libmissioncore_x4.so": binary}
for item in lock["files"]:
if item["path"].startswith(("bin/", "lib/")):
files[item["path"]] = (sdk / item["path"]).read_bytes()
wheels = json.loads((PACKAGING / "python-lock.json").read_text())["wheels"]
for item in wheels:
source = REPOSITORY / "apps/node-agent/build/realsense-wheels" / item["name"]
data = source.read_bytes()
if source.is_symlink() or len(data) != item["bytes"] or digest(data) != item["sha256"]:
raise ValueError("Pinned Python wheel mismatch")
with zipfile.ZipFile(io.BytesIO(data)) as archive:
for entry in archive.infolist():
if entry.is_dir():
continue
name = entry.filename
path = PurePosixPath(name)
if (
path.is_absolute()
or ".." in path.parts
or path.as_posix() != name
or "\\" in name
or ".data/" in name
or name.endswith(".pth")
or (entry.external_attr >> 16) & 0o170000 == 0o120000
):
raise ValueError("Unsupported Python wheel member")
target = "python/" + name
content = archive.read(entry)
if target in files and files[target] != content:
raise ValueError("Python runtime files collide")
files[target] = content
sdk_python = REPOSITORY / "packages/plugin-sdk/python/missioncore_plugin_sdk"
for path in sdk_python.rglob("*.py"):
files["python/missioncore_plugin_sdk/" + path.relative_to(sdk_python).as_posix()] = (
path.read_bytes()
)
payload = archive_bytes(files)
result = {
"schema": "missioncore.insta360.runtime-bundle/v1",
"version": VERSION,
"platform": "ubuntu-24.04-amd64",
"python": "3.12",
"sdk_lock_sha256": digest((PACKAGING / "sdk-lock.json").read_bytes()),
"python_lock_sha256": digest((PACKAGING / "python-lock.json").read_bytes()),
"native": provenance,
"runtime_source_sha256": {
path.name: digest(path.read_bytes()) for path in sorted((ROOT / "runtime").glob("*.py"))
},
"payload_sha256": digest(payload),
"files": {
name: {"bytes": len(data), "sha256": digest(data)}
for name, data in sorted(files.items())
},
}
result["revision"] = digest(json.dumps(result, sort_keys=True).encode())[:24]
return payload, result
def build(output):
payload, bundle = runtime_payload()
files = [
("usr/share/mission-core-node/insta360/payload.zip", payload, 0o644),
(
"usr/share/mission-core-node/insta360/bundle.json",
(json.dumps(bundle, indent=2) + "\n").encode(),
0o644,
),
]
for name in ("sdk-lock.json", "python-lock.json"):
files.append(
(
"usr/share/doc/mission-core-insta360-x4/" + name,
(PACKAGING / name).read_bytes(),
0o644,
)
)
for name in ("layout.py", "bootstrap.py", "supervisor.py", "prepare.py"):
files.append(
("usr/lib/mission-core-node/insta360/" + name, (PACKAGING / name).read_bytes(), 0o644)
)
for path in sorted((ROOT / "runtime").glob("*.py")):
files.append(
("usr/lib/mission-core-node/insta360/runtime/" + path.name, path.read_bytes(), 0o644)
)
for path in sorted(PACKAGING.glob("*.service")):
files.append(("usr/lib/systemd/system/" + path.name, path.read_bytes(), 0o644))
files.extend(
[
(
"usr/lib/udev/rules.d/70-mission-core-insta360.rules",
(PACKAGING / "70-mission-core-insta360.rules").read_bytes(),
0o644,
),
(
"usr/share/polkit-1/rules.d/50-mission-core-insta360.rules",
(PACKAGING / "50-mission-core-insta360.rules").read_bytes(),
0o644,
),
]
)
provenance = {
"package": "mission-core-insta360-x4",
"version": VERSION,
"revision": bundle["revision"],
"files": {name: digest(data) for name, data, _ in files},
"hardware_qualified": False,
"clean_image_qualified": False,
}
files.append(
(
"usr/share/doc/mission-core-insta360-x4/provenance.json",
(json.dumps(provenance, indent=2) + "\n").encode(),
0o644,
)
)
control = f"""Package: mission-core-insta360-x4
Version: {VERSION}
Architecture: amd64
Maintainer: NODE.DC local build <noreply@example.invalid>
Section: admin
Priority: optional
Depends: mission-core-node (>= 0.8.16), mission-core-node (<< 0.9.0),
systemd (>= 255), python3 (>= 3.12), python3 (<< 3.13), adduser, udev, polkitd,
libc6 (>= 2.35), libstdc++6 (>= 12), libgcc-s1, zlib1g
Description: Optional Insta360 X4 control and operator camera integration
Private USB instances and pinned offline runtime for Ubuntu 24.04 amd64.
""".encode()
controls = [("control", control, 0o644)] + [
(name, (PACKAGING / name).read_bytes(), 0o755)
for name in ("preinst", "postinst", "prerm", "postrm")
]
data = package(controls, files)
output.parent.mkdir(parents=True, exist_ok=True)
descriptor = os.open(output, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
with os.fdopen(descriptor, "wb") as stream:
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
return {
"file": str(output),
"bytes": len(data),
"sha256": digest(data),
"revision": bundle["revision"],
"version": VERSION,
}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
print(json.dumps(build(args.output)))
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Build the private X4 adapter through a versioned Linux build artifact.
Input SDK bytes must already satisfy sdk-lock.json. No network, dependency
installation, SDK execution, device access or mutation of system directories.
"""
import argparse
import hashlib
import json
import os
import platform
import shutil
import subprocess
import tempfile
from pathlib import Path
from fetch_sdk import LOCK, ROOT, inspect_elf, verify
def build(sdk, destination):
if platform.system() != "Linux" or platform.machine() != "x86_64":
raise RuntimeError("Native compilation requires Linux x86_64 and the declared compiler")
lock = json.loads(LOCK.read_text())
verify(sdk, lock)
if destination.exists():
raise ValueError("Do not replace a previously built adapter")
destination.parent.mkdir(parents=True, exist_ok=True)
stage = Path(tempfile.mkdtemp(prefix=".native-", dir=destination.parent))
env = {"PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "SOURCE_DATE_EPOCH": "1788825600"}
try:
target = stage / "libmissioncore_x4.so"
subprocess.run(
[
"/usr/bin/g++",
"-std=c++17",
"-O2",
"-Wall",
"-Wextra",
"-Werror",
"-fPIC",
"-fvisibility=hidden",
"-shared",
"-pthread",
"-ffile-prefix-map=" + str(ROOT) + "=/source/insta360-x4",
"-I",
str(sdk / "include"),
str(ROOT / "native/bridge.cpp"),
"-L",
str(sdk / "lib"),
"-Wl,-z,defs,-z,relro,-z,now",
"-Wl,-rpath,$ORIGIN",
"-lCameraSDK",
"-o",
str(target),
],
env=env,
check=True,
timeout=120,
)
elf = inspect_elf(target)
if "libCameraSDK.so" not in elf["needed"]:
raise ValueError("Built adapter does not depend on the admitted SDK")
report = {
"schema": "missioncore.insta360.native-build/v1",
"abi": 1,
"sdk_lock_sha256": hashlib.sha256(LOCK.read_bytes()).hexdigest(),
"source": {
p.name: hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted((ROOT / "native").glob("*"))
if p.is_file()
},
"compiler": subprocess.check_output(
["/usr/bin/g++", "--version"], env=env, text=True
).splitlines()[0],
"binary": {
"name": target.name,
"bytes": target.stat().st_size,
"sha256": hashlib.sha256(target.read_bytes()).hexdigest(),
},
"elf": elf,
"sdk_executed": False,
"hardware_tested": False,
}
target.chmod(0o644)
(stage / "provenance.json").write_text(json.dumps(report, indent=2) + "\n")
os.rename(stage, destination)
return report
finally:
if stage.exists():
shutil.rmtree(stage)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--sdk", type=Path, default=ROOT / "build/sdk")
parser.add_argument("--output", type=Path, default=ROOT / "build/native")
args = parser.parse_args()
print(json.dumps(build(args.sdk.resolve(), args.output.resolve()), indent=2))
@@ -0,0 +1,9 @@
"""Isolated interpreter bootstrap for the fixed, verified build module."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from build_native import ROOT, build # noqa: E402
build(ROOT / "build/sdk", ROOT / "build/native")
@@ -0,0 +1,67 @@
"""Wrap the qualified .deb and the fixed local installer into one owned artifact."""
import argparse
import hashlib
import json
import re
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def build(qualified):
expected = json.loads((qualified / "package-result.json").read_text())
version = expected["version"]
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[1-9][0-9]*)?", version):
raise ValueError("Invalid qualified version")
package_name = "mission-core-insta360-x4_" + version + "_amd64.deb"
package = (qualified / package_name).read_bytes()
if (
len(package) != expected["bytes"]
or hashlib.sha256(package).hexdigest() != expected["sha256"]
):
raise ValueError("Qualified package changed")
files = {
package_name: package,
**{
name: (ROOT / "packaging" / name).read_bytes()
for name in ("install", "install_release.py")
},
}
entrypoint = (ROOT / "packaging/owner_release_entry.py").read_bytes()
manifest = {
"schema": "missioncore.insta360.owner-release/v1",
"version": version,
"runtime_revision": expected["revision"],
"qualification_profile": "sdk-status-and-image-verification",
"qualification": "Ubuntu native build, cold imports, static closure, synthetic tests",
"hardware_qualified": False,
"clean_image_qualified": False,
"entrypoint_sha256": hashlib.sha256(entrypoint).hexdigest(),
"files": {
name: {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
for name, data in files.items()
},
}
raw = json.dumps(manifest, sort_keys=True, indent=2).encode() + b"\n"
ident = hashlib.sha256(raw).hexdigest()[:24]
output = ROOT / "build" / ("mission-core-x4-install-" + ident + ".pyz")
with output.open("xb") as stream, zipfile.ZipFile(stream, "w") as archive:
for name, data in {"__main__.py": entrypoint, "release.json": raw, **files}.items():
info = zipfile.ZipInfo(name, date_time=(2026, 9, 8, 0, 0, 0))
info.external_attr = 0o600 << 16
archive.writestr(info, data)
output.chmod(0o600)
return {
"artifact": str(output),
"release_id": ident,
"bytes": output.stat().st_size,
"sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--qualified", type=Path, required=True)
print(json.dumps(build(parser.parse_args().qualified)))
@@ -0,0 +1,84 @@
"""Snapshot package inputs for compilation/qualification only on the Ubuntu Mini."""
import hashlib
import json
import sys
import zipfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from build_deb import VERSION # noqa: E402
from fetch_sdk import ROOT, verify # noqa: E402
REPOSITORY = ROOT.parents[1]
def build():
sdk = json.loads((ROOT / "packaging/sdk-lock.json").read_text())
verify(ROOT / "build/sdk", sdk)
paths = list((ROOT / "packaging").glob("*.py"))
paths += [
p
for pattern in ("*.json", "*.service", "*.rules")
for p in (ROOT / "packaging").glob(pattern)
]
paths += [ROOT / "packaging" / name for name in ("preinst", "postinst", "prerm", "postrm")]
paths += list((ROOT / "runtime").glob("*.py")) + list((ROOT / "tests").glob("*.py"))
paths += [ROOT / "native" / name for name in ("bridge.cpp", "bridge.h")]
paths += [ROOT / "build/native" / name for name in ("libmissioncore_x4.so", "provenance.json")]
paths += [ROOT / "build/sdk" / entry["path"] for entry in sdk["files"]]
wheels = json.loads((ROOT / "packaging/python-lock.json").read_text())["wheels"]
paths += [
REPOSITORY / "apps/node-agent/build/realsense-wheels" / entry["name"] for entry in wheels
]
paths += list((REPOSITORY / "packages/plugin-sdk/python/missioncore_plugin_sdk").rglob("*.py"))
paths += [REPOSITORY / "scripts/packaging/debian.py"]
paths += [REPOSITORY / "apps/node-agent/packaging/insta360_profile.py"]
paths += [
REPOSITORY / name
for name in (
"src/k1link/__init__.py",
"src/k1link/fleet/__init__.py",
"src/k1link/fleet/sensors.py",
"src/k1link/fleet/trust.py",
)
]
files = {
path.relative_to(REPOSITORY).as_posix(): path.read_bytes() for path in sorted(set(paths))
}
entrypoint = (ROOT / "packaging/native_build_entry.py").read_bytes()
manifest = {
"schema": "missioncore.insta360.build-source/v1",
"version": "0.1.0",
"kind": "package",
"package_version": VERSION,
"entrypoint_sha256": hashlib.sha256(entrypoint).hexdigest(),
"files": {
name: {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
for name, data in files.items()
},
}
raw = json.dumps(manifest, sort_keys=True, indent=2).encode() + b"\n"
ident = hashlib.sha256(raw).hexdigest()[:24]
output = ROOT / "build" / ("mission-core-x4-package-build-" + ident + ".pyz")
with output.open("xb") as stream, zipfile.ZipFile(stream, "w") as archive:
for name, data in {
"__main__.py": entrypoint,
"source.json": raw,
**{"source/" + name: data for name, data in files.items()},
}.items():
info = zipfile.ZipInfo(name, date_time=(2026, 9, 8, 0, 0, 0))
info.external_attr = 0o600 << 16
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, data)
output.chmod(0o600)
return {
"artifact": str(output),
"source_id": ident,
"bytes": output.stat().st_size,
"sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
}
if __name__ == "__main__":
print(json.dumps(build()))
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Package exact native sources and locked SDK input for the Ubuntu build step."""
import hashlib
import json
import zipfile
from pathlib import Path
from fetch_sdk import LOCK, ROOT, verify
def build():
lock = json.loads(LOCK.read_text())
sdk = ROOT / "build/sdk"
verify(sdk, lock)
names = [
"native/bridge.cpp",
"native/bridge.h",
"native/tests/fake_sdk.cpp",
"native/tests/check_abi.py",
"packaging/build_native.py",
"packaging/build_native_entry.py",
"packaging/fetch_sdk.py",
"packaging/sdk-lock.json",
]
names += ["build/sdk/" + entry["path"] for entry in lock["files"]]
files = {name: (ROOT / name).read_bytes() for name in sorted(names)}
entrypoint = (Path(__file__).with_name("native_build_entry.py")).read_bytes()
manifest = {
"schema": "missioncore.insta360.build-source/v1",
"version": "0.1.0",
"entrypoint_sha256": hashlib.sha256(entrypoint).hexdigest(),
"files": {
name: {"bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()}
for name, data in files.items()
},
}
content = json.dumps(manifest, sort_keys=True, indent=2).encode() + b"\n"
ident = hashlib.sha256(content).hexdigest()[:24]
output = ROOT / "build" / ("mission-core-x4-build-" + ident + ".pyz")
entries = {"__main__.py": entrypoint, "source.json": content}
entries.update({"source/" + name: data for name, data in files.items()})
with (
output.open("xb") as stream,
zipfile.ZipFile(stream, "w", compression=zipfile.ZIP_DEFLATED) as archive,
):
for name, data in entries.items():
info = zipfile.ZipInfo(name, date_time=(2026, 9, 8, 0, 0, 0))
info.external_attr = 0o600 << 16
info.compress_type = zipfile.ZIP_DEFLATED
archive.writestr(info, data)
output.chmod(0o600)
return {
"artifact": str(output),
"bytes": output.stat().st_size,
"sha256": hashlib.sha256(output.read_bytes()).hexdigest(),
"source_id": ident,
}
if __name__ == "__main__":
print(json.dumps(build()))
@@ -0,0 +1,84 @@
"""Cold Python imports and synthetic installer/control tests, no vendor execution."""
import hashlib
import importlib
import io
import json
import shutil
import subprocess
import sys
import tarfile
import unittest
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(ROOT), str(ROOT / "packaging")]
from build_deb import VERSION # noqa: E402
from fetch_sdk import inspect_elf # noqa: E402
from prepare import entries # noqa: E402
package = ROOT / "build" / ("mission-core-insta360-x4_" + VERSION + "_amd64.deb")
data = subprocess.check_output(["/usr/bin/dpkg-deb", "--fsys-tarfile", str(package)], timeout=30)
with tarfile.open(fileobj=io.BytesIO(data)) as archive:
payload = archive.extractfile("usr/share/mission-core-node/insta360/payload.zip").read()
bundle = json.load(archive.extractfile("usr/share/mission-core-node/insta360/bundle.json"))
if hashlib.sha256(payload).hexdigest() != bundle["payload_sha256"]:
raise ValueError("Package payload hash mismatch")
stage = ROOT / "build/cold-runtime"
stage.mkdir(mode=0o700)
try:
with zipfile.ZipFile(io.BytesIO(payload)) as archive:
for name, content in entries(archive, bundle):
path = stage / name
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
path.write_bytes(content)
python = stage / "python"
sys.path.insert(0, str(python))
versions = {}
for name in ("aiohttp", "aiortc", "av", "pydantic", "cryptography", "cffi"):
module = importlib.import_module(name)
if not Path(module.__file__).is_relative_to(python):
raise RuntimeError("An undeclared system Python package was imported")
versions[name] = getattr(module, "__version__", "available")
libraries = [
path for path in stage.rglob("*") if path.is_file() and path.read_bytes()[:4] == b"\x7fELF"
]
provided = {path.name for path in libraries}
system = {
"libc.so.6",
"libm.so.6",
"libmvec.so.1",
"libpthread.so.0",
"libdl.so.2",
"librt.so.1",
"libresolv.so.2",
"libgcc_s.so.1",
"libstdc++.so.6",
"ld-linux-x86-64.so.2",
"libz.so.1",
}
closure = {str(path.relative_to(stage)): inspect_elf(path)["needed"] for path in libraries}
missing = {
name for needed in closure.values() for name in needed if name not in provided | system
}
if missing:
raise RuntimeError("Undeclared OS library dependencies: " + ", ".join(sorted(missing)))
(ROOT / "build/runtime-check.json").write_text(
json.dumps(
{
"python_imports": versions,
"elf_dependencies": closure,
"declared_os_libraries": sorted(system),
"sdk_executed": False,
"clean_os_image_tested": False,
},
indent=2,
)
+ "\n"
)
suite = unittest.defaultTestLoader.discover(str(ROOT / "tests"), pattern="check_*.py")
if not unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful():
raise RuntimeError("Package qualification tests failed")
finally:
shutil.rmtree(stage)
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""Build-time SDK acquisition. Never installed or executed on an operator Node.
Pin every byte from a public mirror; do not execute SDK code to inspect it.
The source lock is provenance, not a claim of vendor authenticity or licensing.
"""
import argparse
import hashlib
import json
import re
import shutil
import struct
import tempfile
import urllib.request
from pathlib import Path, PurePosixPath
ROOT = Path(__file__).resolve().parents[1]
LOCK = Path(__file__).with_name("sdk-lock.json")
def entries(lock):
if lock["schema"] != "missioncore.insta360.sdk-source/v1":
raise ValueError("Unsupported SDK lock")
if not re.fullmatch(r"[0-9a-f]{40}", lock["commit"]):
raise ValueError("SDK source must be an immutable commit")
seen = set()
for entry in lock["files"]:
path = PurePosixPath(entry["path"])
if (
path.is_absolute()
or ".." in path.parts
or "\\" in entry["path"]
or path.as_posix() != entry["path"]
or entry["path"] in seen
or not re.fullmatch(r"[0-9a-f]{64}", entry["sha256"])
or not 0 < entry["bytes"] <= 32 * 1024 * 1024
):
raise ValueError("Invalid SDK payload entry")
seen.add(entry["path"])
yield entry
def verified_bytes(data, entry):
if len(data) != entry["bytes"] or hashlib.sha256(data).hexdigest() != entry["sha256"]:
raise ValueError("SDK checksum mismatch: " + entry["path"])
return data
def inspect_elf(path):
"""Parse ELF64 sections without loading the library (never ldd/dlopen)."""
data = path.read_bytes()
if (
len(data) < 64
or data[:7] != b"\x7fELF\x02\x01\x01"
or struct.unpack_from("<H", data, 18)[0] != 62
):
raise ValueError("SDK is not Linux x86_64 ELF64")
offset = struct.unpack_from("<Q", data, 40)[0]
size, count = struct.unpack_from("<HH", data, 58)
if size != 64 or not 0 < count < 4096 or offset + count * size > len(data):
raise ValueError("Invalid ELF section table")
sections = [struct.unpack_from("<IIQQQQIIQQ", data, offset + i * size) for i in range(count)]
needed = []
for section in sections:
if section[1] != 6: # SHT_DYNAMIC
continue
if section[6] >= count:
raise ValueError("Invalid ELF string table")
strings = sections[section[6]]
table = data[strings[4] : strings[4] + strings[5]]
if section[4] + section[5] > len(data) or section[5] % 16:
raise ValueError("Invalid ELF dynamic table")
for at in range(section[4], section[4] + section[5], 16):
tag, value = struct.unpack_from("<qQ", data, at)
if tag == 1: # DT_NEEDED
end = table.find(b"\0", value)
if end < value:
raise ValueError("Invalid ELF dependency name")
needed.append(table[value:end].decode("ascii"))
versions = sorted(
set(x.decode() for x in re.findall(rb"(?:GLIBCXX|GLIBC|CXXABI)_[0-9.]+", data))
)
return {"architecture": "linux-x86_64", "needed": sorted(needed), "abi_versions": versions}
def verify(folder, lock):
for entry in entries(lock):
path = folder / entry["path"]
if any(p.is_symlink() for p in (path, *path.parents)):
raise ValueError("SDK payload cannot contain symlinks")
verified_bytes(path.read_bytes(), entry)
return inspect_elf(folder / "lib/libCameraSDK.so")
def fetch(destination, source=None):
lock = json.loads(LOCK.read_text())
if destination.exists():
return verify(destination, lock)
destination.parent.mkdir(parents=True, exist_ok=True)
stage = Path(tempfile.mkdtemp(prefix=".sdk-", dir=destination.parent))
try:
for entry in entries(lock):
if source:
path = source / entry["path"]
if path.is_symlink():
raise ValueError("SDK source cannot be a symlink")
data = path.read_bytes()
else:
host = (
"https://media.githubusercontent.com/media/"
if entry["lfs"]
else "https://raw.githubusercontent.com/"
)
repo = lock["repository"].removeprefix("https://github.com/")
url = host + "/".join((repo, lock["commit"], lock["directory"], entry["path"]))
with urllib.request.urlopen(url, timeout=45) as response:
data = response.read(entry["bytes"] + 1)
target = stage / entry["path"]
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(verified_bytes(data, entry))
target.chmod(0o644)
report = verify(stage, lock)
stage.rename(destination)
return report
finally:
if stage.exists():
shutil.rmtree(stage)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--destination", type=Path, default=ROOT / "build/sdk")
parser.add_argument(
"--source", type=Path, help="Import an already acquired SDK using the same lock"
)
parser.add_argument(
"--check", action="store_true", help="Verify existing payload without network access"
)
args = parser.parse_args()
result = (
verify(args.destination, json.loads(LOCK.read_text()))
if args.check
else fetch(args.destination, args.source)
)
print(json.dumps(result, indent=2))
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
set -euo pipefail
umask 077
mc_x4_release_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
if [ ! -t 0 ]; then
exec /usr/bin/gnome-terminal --wait --title="Mission Core · Insta360 X4" -- "$mc_x4_release_dir/install"
fi
printf '%s\n' 'Mission Core · Insta360 X4' 'Установка драйвера и проверка подключения SDK на этом Ubuntu-компьютере.' 'Пароль администратора вводится только в этом локальном окне Ubuntu.'
set +e
/usr/bin/sudo /usr/bin/python3 -I "$mc_x4_release_dir/install_release.py" 2>&1 | /usr/bin/tee -a "$mc_x4_release_dir/install-output.log"
mc_x4_install_result=${PIPESTATUS[0]}
printf '\nКод завершения: %s\nНажмите Enter, чтобы закрыть окно.\n' "$mc_x4_install_result"
read -r mc_x4_close
exit "$mc_x4_install_result"
@@ -0,0 +1,263 @@
"""Owner-facing, versioned installer; first SDK Open belongs to fixed prepare.
Invoke through the release's local Ubuntu terminal. The OS password is never
accepted by this script or sent to Mission Core. No arbitrary commands/URLs.
"""
import hashlib
import http.client
import json
import os
import re
import socket
import subprocess
import sys
import time
import uuid
from contextlib import suppress
from datetime import UTC, datetime, timedelta
from pathlib import Path
STAGING = Path("/var/tmp/mission-core-x4-installs")
SERVICES = (
"mission-core-node.service",
"mission-core-k1.service",
"mission-core-realsense.service",
)
def private(path):
path.mkdir(mode=0o700, exist_ok=True)
info = path.lstat()
if path.is_symlink() or not path.is_dir() or info.st_uid != 0 or info.st_mode & 0o077:
raise RuntimeError("Installer staging is not root-owned and private")
def request(path, operation=None):
timeout = 55 if operation else 3
client = http.client.HTTPConnection("driver", timeout=timeout)
client.sock = socket.socket(socket.AF_UNIX)
client.sock.settimeout(timeout)
try:
client.sock.connect(str(path))
if operation is None:
client.request("GET", "/snapshot")
else:
client.request(
"POST", "/operation", json.dumps(operation), {"Content-Type": "application/json"}
)
response = client.getresponse()
content = response.read(65537)
if response.status != 200 or len(content) > 65536:
raise RuntimeError("Camera status is unavailable")
return json.loads(content)
finally:
client.close()
def main():
if os.geteuid() or sys.argv[1:]:
raise RuntimeError("Запустите установщик в локальном окне Ubuntu через sudo.")
os.umask(0o077)
source = Path(__file__).resolve().parent
manifest = json.loads((source / "release.json").read_text())
if manifest["schema"] != "missioncore.insta360.owner-release/v1":
raise ValueError("Unsupported release")
version = manifest["version"]
revision = manifest["runtime_revision"]
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[1-9][0-9]*)?", version) or not re.fullmatch(
r"[0-9a-f]{24}", revision
):
raise ValueError("Invalid release version")
if manifest["qualification_profile"] != "sdk-status-and-image-verification":
raise ValueError("Unsupported qualification profile")
package = "mission-core-insta360-x4_" + version + "_amd64.deb"
admitted = manifest["files"]
if set(admitted) != {package, "install_release.py", "install"}:
raise ValueError("Unexpected installer contents")
for name, expected in admitted.items():
data = (source / name).read_bytes()
if len(data) != expected["bytes"] or hashlib.sha256(data).hexdigest() != expected["sha256"]:
raise ValueError("Установщик повреждён. Контрольная сумма не совпала.")
private(STAGING)
identifier = uuid.uuid4().hex
folder = STAGING / identifier
private(folder)
# Snapshot the exact admitted bytes to root-only staging before APT, so
# it never consumes a mutable package from the operator's Downloads path.
data = (source / package).read_bytes()
if hashlib.sha256(data).hexdigest() != admitted[package]["sha256"]:
raise ValueError("Package changed before staging")
(folder / package).write_bytes(data)
report = {
"schema": "missioncore.insta360.install-run/v1",
"session_id": identifier,
"started_at": datetime.now(UTC).isoformat(),
"monotonic_started": time.monotonic(),
"release_sha256": hashlib.sha256((source / "release.json").read_bytes()).hexdigest(),
"package_sha256": admitted[package]["sha256"],
"state": "running",
"scope": "install-profile-SDK-status-and-bounded-image-verification",
"steps": [],
}
env = {
"PATH": "/usr/sbin:/usr/bin:/sbin:/bin",
"LANG": "C.UTF-8",
"DEBIAN_FRONTEND": "noninteractive",
}
def publish():
(folder / "report.json").write_text(json.dumps(report, indent=2) + "\n")
def run(step, command, timeout=180):
report["steps"].append(
{"id": step, "state": "running", "started_at": datetime.now(UTC).isoformat()}
)
publish()
result = subprocess.run(command, env=env, capture_output=True, timeout=timeout)
(folder / (step + ".stdout")).write_bytes(result.stdout)
(folder / (step + ".stderr")).write_bytes(result.stderr)
report["steps"][-1].update(
state="complete" if result.returncode == 0 else "error", exit_code=result.returncode
)
publish()
if result.returncode:
raise RuntimeError("Не завершён этап установки: " + step)
return result.stdout
publish()
try:
# These remain private to the authenticated owner; the terminal wrapper
# saves stdout with umask 077. No camera identifiers enter public logs.
previous = sorted(STAGING.glob("*/report.json"), key=lambda p: p.stat().st_mtime)
for path in previous[-6:]:
if path.parent != folder:
print("MISSION_CORE_X4_PREVIOUS " + path.read_text().replace("\n", ""), flush=True)
baseline = run(
"baseline",
[
"/usr/bin/systemctl",
"show",
*SERVICES,
"-p",
"Id",
"-p",
"ActiveState",
"-p",
"NRestarts",
],
)
report["existing_services"] = baseline.decode().splitlines()
run(
"apt-plan",
["/usr/bin/apt-get", "--simulate", "--no-remove", "install", str(folder / package)],
)
run(
"package",
["/usr/bin/apt-get", "install", "-y", "--no-remove", str(folder / package)],
timeout=None,
)
# This is the identical installed model job started by Node's local or
# paired remote prepare command. No SDK demo, root SDK or manual grant.
preparation = Path("/var/lib/mission-core-insta360/preparation.json")
prepared = json.loads(preparation.read_text()) if preparation.exists() else {}
active = Path("/var/lib/mission-core-insta360/active.path")
# An upgrade's postinst already executes this same fixed preparation.
# Do not race its asynchronously connecting workers with a second run.
if not (
prepared.get("state") == "complete"
and prepared.get("revision") == revision
and active.exists()
and active.read_text().strip() == revision
):
run(
"prepare",
["/usr/bin/systemctl", "start", "mission-core-node-insta360-x4-prepare.service"],
)
deadline = time.monotonic() + 50
while True:
values = []
for path in Path("/run/mission-core-x4-instances").glob("instax4_*/driver.sock"):
if not re.fullmatch(r"instax4_[0-9a-f]{32}", path.parent.name):
continue
with suppress(OSError, ValueError, RuntimeError, http.client.HTTPException):
values.append(request(path))
if values and all(item.get("prepared") and item.get("online") for item in values):
break
if time.monotonic() >= deadline:
report["camera_status"] = values
raise RuntimeError(
"Пакет установлен, но подключение X4 не подтверждено. "
"Проверьте питание и USB-режим камеры."
)
time.sleep(1)
report["camera_status"] = values
if len(values) != 1:
raise RuntimeError("Для этой аппаратной проверки требуется ровно одна X4.")
camera = values[0]
if not re.fullmatch(r"instax4_[0-9a-f]{32}", camera["id"]):
raise RuntimeError("Camera identity is invalid")
now = datetime.now(UTC)
operation = "op_" + uuid.uuid4().hex
command = {
"api_version": "missioncore.nodedc/plugin-sdk/v0alpha2",
"kind": "OperationRequest",
"operation_id": operation,
"idempotency_key": operation,
"session": {"device_id": camera["id"], "session_id": camera["session_id"]},
"action_id": "verify",
"requested_at": now.isoformat(),
"deadline_at": (now + timedelta(seconds=60)).isoformat(),
"parameters": {},
}
report["image_verification"] = {"request": command, "state": "running"}
publish()
result = request(
Path("/run/mission-core-x4-instances") / camera["id"] / "driver.sock", command
)
report["image_verification"].update(state=result.get("state"), result=result)
publish()
if (
result.get("state") != "complete"
or result.get("result", {}).get("verified") is not True
):
raise RuntimeError("SDK подключён, но проверка изображения не завершилась успешно.")
after = run(
"existing-services-after",
[
"/usr/bin/systemctl",
"show",
*SERVICES,
"-p",
"Id",
"-p",
"ActiveState",
"-p",
"NRestarts",
],
)
report["existing_services_unchanged"] = after == baseline
report["state"] = "complete"
print("Пакет X4 установлен. SDK подключился; получение изображения подтверждено.")
print("WebRTC и команды записи остаются отдельными проверками.")
except Exception as error:
report["state"] = "error"
report["error"] = str(error)[:300]
raise
finally:
report["finished_at"] = datetime.now(UTC).isoformat()
report["duration_seconds"] = time.monotonic() - report["monotonic_started"]
publish()
# Retain bounded private evidence and remove only the installer-owned
# temporary package copy. Runtime/journals remain owned by the .deb.
(folder / package).unlink()
print("MISSION_CORE_X4_RESULT " + json.dumps(report, ensure_ascii=False), flush=True)
print("Отчёт установки:", folder / "report.json")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(str(error), file=sys.stderr)
sys.exit(1)
+60
View File
@@ -0,0 +1,60 @@
"""Fixed root-owned locations shared by installer and runtime bootstrap."""
import json
import os
from pathlib import Path
CODE = Path("/usr/lib/mission-core-node/insta360")
SHARE = Path("/usr/share/mission-core-node/insta360")
STATE = Path("/var/lib/mission-core-insta360")
CONTROL = Path("/run/mission-core-x4-control")
def trusted(path, directory=False):
info = path.lstat()
if path.is_symlink() or info.st_uid != 0 or info.st_mode & 0o022:
raise RuntimeError("Untrusted camera installation path")
if path.is_dir() != directory:
raise RuntimeError("Unexpected camera installation entry")
return path
def manifest():
trusted(SHARE, True)
return json.loads(trusted(SHARE / "bundle.json").read_text())
def active():
trusted(STATE, True)
ident = trusted(STATE / "active.path").read_text().strip()
if len(ident) != 24 or any(c not in "0123456789abcdef" for c in ident):
raise RuntimeError("Invalid installed runtime revision")
return trusted(STATE / "runtime", True) / ident
def directory(path, mode=0o755):
path.mkdir(mode=mode, exist_ok=True)
trusted(path, True)
def write(path, data, mode=0o644):
import tempfile
if path.exists() or path.is_symlink():
trusted(path)
fd, temporary = tempfile.mkstemp(prefix=".x4-", dir=path.parent)
try:
with os.fdopen(fd, "wb") as stream:
os.fchmod(stream.fileno(), mode)
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
folder = os.open(path.parent, os.O_RDONLY)
try:
os.fsync(folder)
finally:
os.close(folder)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
@@ -0,0 +1,26 @@
[Unit]
Description=Mission Core X4 USB instance supervisor
After=systemd-udevd.service
ConditionPathExists=/var/lib/mission-core-insta360/active.path
[Service]
Type=simple
ExecStart=/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/supervisor.py
RuntimeDirectory=mission-core-x4-control
RuntimeDirectoryMode=0755
RuntimeDirectoryPreserve=yes
UMask=0022
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ReadWritePaths=/run/systemd/system
RestrictAddressFamilies=AF_UNIX
CapabilityBoundingSet=
TasksMax=16
MemoryMax=96M
Restart=no
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,31 @@
[Unit]
Description=Mission Core Insta360 model broker
ConditionPathExists=/var/lib/mission-core-insta360/active.path
[Service]
Type=simple
User=mission-core-insta360
Group=mission-core-node
ExecStart=/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/bootstrap.py broker
RuntimeDirectory=mission-core-insta360
RuntimeDirectoryMode=0750
StateDirectory=mission-core-insta360-broker
StateDirectoryMode=0700
UMask=0007
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
CapabilityBoundingSet=
TasksMax=192
MemoryMax=1G
CPUQuota=150%
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,8 @@
[Unit]
Description=Mission Core fixed Insta360 X4 preparation
After=systemd-udevd.service
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/prepare.py
TimeoutStartSec=180
UMask=0022
@@ -0,0 +1,203 @@
"""Entry point embedded in the versioned, self-verifying Ubuntu build artifact.
No system install, SDK loading, discovery, camera command or root execution.
All writes belong to this private build directory and its immutable archive.
"""
import hashlib
import io
import json
import os
import platform
import re
import resource
import shutil
import subprocess
import sys
import tarfile
import time
import zipfile
from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
BUILD_ROOT = Path("/var/tmp/mission-core-x4-builds")
def digest(data):
return hashlib.sha256(data).hexdigest()
def private_directory(path):
path.mkdir(mode=0o700, exist_ok=True)
info = path.lstat()
if (
path.is_symlink()
or not path.is_dir()
or info.st_uid != os.geteuid()
or info.st_mode & 0o077
):
raise RuntimeError("Build directory is not private and owned")
def main():
if os.geteuid() == 0 or platform.system() != "Linux" or platform.machine() != "x86_64":
raise RuntimeError("Use the unprivileged Ubuntu x86_64 build account")
os.umask(0o077)
started, monotonic = datetime.now(UTC).isoformat(), time.monotonic()
artifact = Path(sys.argv[0]).resolve()
with zipfile.ZipFile(artifact) as archive:
raw_manifest = archive.read("source.json")
manifest = json.loads(raw_manifest)
ident = digest(raw_manifest)[:24]
if manifest["schema"] != "missioncore.insta360.build-source/v1":
raise ValueError("Unsupported build artifact")
if digest(archive.read("__main__.py")) != manifest["entrypoint_sha256"]:
raise ValueError("Build entry point differs from the manifest")
private_directory(BUILD_ROOT)
folder = BUILD_ROOT / ident
private_directory(folder)
output = folder / "result.tar.gz"
if len(sys.argv) > 1:
if sys.argv[1:] != ["--clean"]:
raise ValueError("No build commands or paths are accepted")
shutil.rmtree(folder)
print(json.dumps({"cleaned": ident}))
return
if output.exists():
print(
json.dumps(
{"result": str(output), "sha256": digest(output.read_bytes()), "reused": True}
)
)
return
if (folder / "source").exists():
raise RuntimeError(
"Incomplete prior attempt; preserve evidence and use --clean before retry"
)
source = folder / "source"
source.mkdir(mode=0o700)
allowed = {"__main__.py", "source.json"} | {"source/" + p for p in manifest["files"]}
if len(archive.namelist()) != len(allowed) or set(archive.namelist()) != allowed:
raise ValueError("Unexpected source archive entries")
for relative, expected in manifest["files"].items():
path = PurePosixPath(relative)
if (
path.is_absolute()
or ".." in path.parts
or path.as_posix() != relative
or "\\" in relative
):
raise ValueError("Unsafe build payload path")
entry = archive.getinfo("source/" + relative)
if entry.file_size != expected["bytes"] or entry.file_size > 100 * 1024 * 1024:
raise ValueError("Invalid source size")
data = archive.read(entry)
if digest(data) != expected["sha256"]:
raise ValueError("Source hash mismatch")
target = source / relative
target.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
target.write_bytes(data)
(folder / "intent.json").write_text(
json.dumps(
{
"schema": "missioncore.insta360.build-run/v1",
"id": ident,
"started_at": started,
"monotonic_started": monotonic,
"artifact_sha256": digest(artifact.read_bytes()),
"source_manifest_sha256": digest(raw_manifest),
"scope": "package-and-synthetic-tests-only"
if manifest.get("kind") == "package"
else "compile-and-synthetic-tests-only",
"state": "running",
},
indent=2,
)
+ "\n"
)
# Bound compiler consumption on the shared Mini. This does not alter OS,
# service, user or shell configuration; limits apply to this process tree.
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
resource.setrlimit(resource.RLIMIT_CPU, (180, 180))
resource.setrlimit(resource.RLIMIT_AS, (1024 * 1024 * 1024, 1024 * 1024 * 1024))
os.nice(10)
env = {"PATH": "/usr/bin:/bin", "LANG": "C.UTF-8", "PYTHONDONTWRITEBYTECODE": "1"}
report = json.loads((folder / "intent.json").read_text())
package_build = manifest.get("kind") == "package"
if package_build:
plugin = source / "plugins/insta360-x4"
version = manifest["package_version"]
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[1-9][0-9]*)?", version):
raise ValueError("Invalid package build version")
package_name = "mission-core-insta360-x4_" + version + "_amd64.deb"
jobs = (
(
"package-build",
["/usr/bin/python3", "-I", str(plugin / "packaging/package_build_entry.py")],
),
(
"package-tests",
["/usr/bin/python3", "-I", str(plugin / "packaging/check_package_entry.py")],
),
)
outputs = [
(
plugin / "build" / package_name,
package_name,
),
(plugin / "build/package-result.json", "package-result.json"),
(plugin / "build/runtime-check.json", "runtime-check.json"),
(folder / "package-tests.stderr", "package-tests.txt"),
]
else:
jobs = (
(
"native-build",
["/usr/bin/python3", "-I", str(source / "packaging/build_native_entry.py")],
),
(
"synthetic-abi-tests",
["/usr/bin/python3", "-I", str(source / "native/tests/check_abi.py"), "-v"],
),
)
outputs = [
(source / "build/native/libmissioncore_x4.so", "libmissioncore_x4.so"),
(source / "build/native/provenance.json", "provenance.json"),
(folder / "synthetic-abi-tests.stderr", "synthetic-abi-tests.txt"),
]
outputs.append((folder / "report.json", "build-report.json"))
try:
for name, command in jobs:
result = subprocess.run(command, cwd=source, env=env, capture_output=True, timeout=150)
(folder / (name + ".stdout")).write_bytes(result.stdout)
(folder / (name + ".stderr")).write_bytes(result.stderr)
if result.returncode:
raise RuntimeError(name + " failed; inspect the private build report")
report.update(
state="complete",
finished_at=datetime.now(UTC).isoformat(),
duration_seconds=time.monotonic() - monotonic,
)
(folder / "report.json").write_text(json.dumps(report, indent=2) + "\n")
data = io.BytesIO()
with tarfile.open(fileobj=data, mode="w:gz") as archive:
for path, name in outputs:
archive.add(path, arcname=name, recursive=False)
output.write_bytes(data.getvalue())
print(
json.dumps(
{"result": str(output), "sha256": digest(output.read_bytes()), "state": "complete"}
)
)
except Exception:
report.update(
state="error",
finished_at=datetime.now(UTC).isoformat(),
duration_seconds=time.monotonic() - monotonic,
)
(folder / "report.json").write_text(json.dumps(report, indent=2) + "\n")
raise
if __name__ == "__main__":
main()
@@ -0,0 +1,149 @@
"""Stage, inspect or open the fixed Ubuntu installer; never collect passwords."""
import hashlib
import json
import os
import platform
import re
import subprocess
import sys
import zipfile
from pathlib import Path
PROFILES = {
"missioncore.insta360.owner-release/v1": (
"mission-core-insta360-x4",
"/var/tmp/mission-core-x4-releases",
"Mission Core · Insta360 X4",
),
"missioncore.node.owner-release/v1": (
"mission-core-node",
"/var/tmp/mission-core-node-releases",
"Mission Core Node",
),
}
def private(path):
path.mkdir(mode=0o700, exist_ok=True)
info = path.lstat()
if path.is_symlink() or info.st_uid != os.geteuid() or info.st_mode & 0o077:
raise RuntimeError("Release directory is not private and owned")
def main():
if os.geteuid() == 0 or platform.system() != "Linux" or platform.machine() != "x86_64":
raise RuntimeError("Откройте установщик обычным пользователем на Ubuntu amd64.")
if sys.argv[1:] not in (["--stage"], ["--plan"], ["--launch"]):
raise ValueError("Use --stage, --plan or --launch")
os.umask(0o077)
with zipfile.ZipFile(sys.argv[0]) as archive:
raw = archive.read("release.json")
manifest = json.loads(raw)
if manifest.get("schema") not in PROFILES:
raise ValueError("Invalid release schema")
package_id, root, title = PROFILES[manifest["schema"]]
version = manifest["version"]
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[1-9][0-9]*)?", version):
raise ValueError("Invalid release version")
package = package_id + "_" + version + "_amd64.deb"
if hashlib.sha256(archive.read("__main__.py")).hexdigest() != manifest["entrypoint_sha256"]:
raise ValueError("Release entry point changed")
allowed = {
"release.json",
"__main__.py",
"install",
"install_release.py",
package,
}
if set(archive.namelist()) != allowed or len(archive.namelist()) != len(allowed):
raise ValueError("Invalid release contents")
if set(manifest["files"]) != allowed - {"release.json", "__main__.py"}:
raise ValueError("Invalid release manifest")
identifier = hashlib.sha256(raw).hexdigest()[:24]
private(Path(root))
folder = Path(root) / identifier
private(folder)
files = {name: archive.read(name) for name in manifest["files"]}
for name, data in files.items():
expected = manifest["files"][name]
if (
len(data) != expected["bytes"]
or hashlib.sha256(data).hexdigest() != expected["sha256"]
):
raise ValueError("Release payload changed")
files["release.json"] = raw
for name, data in files.items():
path = folder / name
if path.is_symlink():
raise ValueError("Unexpected release symlink")
if path.exists():
if path.read_bytes() != data:
raise ValueError("Existing release was modified")
else:
with path.open("xb") as stream:
stream.write(data)
path.chmod(0o700 if name == "install" else 0o600)
print(json.dumps({"release_id": identifier, "directory": str(folder)}), flush=True)
if sys.argv[1] == "--plan":
result = subprocess.run(
[
"/usr/bin/apt-get",
"--simulate",
"--no-remove",
"install",
str(folder / package),
],
capture_output=True,
text=True,
timeout=45,
)
(folder / "apt-plan.stdout").write_text(result.stdout)
(folder / "apt-plan.stderr").write_text(result.stderr)
print(result.stdout)
if result.returncode:
raise RuntimeError("APT plan failed; inspect the release's private report")
elif sys.argv[1] == "--launch":
environment = dict(os.environ)
result = subprocess.run(
["/usr/bin/systemctl", "--user", "show-environment"],
capture_output=True,
text=True,
timeout=5,
check=True,
)
# Only graphical session coordinates are admitted. Other user-service
# environment values are neither passed on nor printed in evidence.
for line in result.stdout.splitlines():
key, separator, value = line.partition("=")
if separator and key in {
"DISPLAY",
"WAYLAND_DISPLAY",
"XDG_RUNTIME_DIR",
"DBUS_SESSION_BUS_ADDRESS",
"XAUTHORITY",
}:
environment[key] = value
with (
(folder / "launcher.stdout").open("ab") as out,
(folder / "launcher.stderr").open("ab") as err,
):
process = subprocess.Popen(
[
"/usr/bin/gnome-terminal",
"--wait",
"--title=" + title + " · " + version,
"--",
str(folder / "install"),
],
env=environment,
stdout=out,
stderr=err,
start_new_session=True,
)
(folder / "launch.json").write_text(json.dumps({"pid": process.pid}) + "\n")
print("Окно установщика открывается в локальном сеансе Ubuntu.")
if __name__ == "__main__":
main()
@@ -0,0 +1,12 @@
"""Fixed package-build entry point within the versioned Ubuntu source artifact."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from build_deb import ROOT, VERSION, build # noqa: E402
result = build(ROOT / "build" / ("mission-core-insta360-x4_" + VERSION + "_amd64.deb"))
(ROOT / "build/package-result.json").write_text(json.dumps(result, indent=2) + "\n")
print(json.dumps(result))
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
set -eu
if [ "$1" = configure ]; then
if ! getent group mission-core-x4-usb >/dev/null; then
addgroup --system mission-core-x4-usb
fi
if ! getent passwd mission-core-insta360 >/dev/null; then
adduser --system --home /var/lib/mission-core-insta360 --no-create-home --disabled-login --ingroup mission-core-node mission-core-insta360
fi
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
if [ -f /var/lib/mission-core-insta360/active.path ]; then
/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/prepare.py
fi
fi
fi
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
set -eu
case "$1" in
remove|purge)
if [ -d /run/systemd/system ]; then
systemctl daemon-reload
udevadm control --reload-rules
fi
;;
esac
# Device state and operation receipts are retained. No SD files are removed.
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
set -eu
if [ -f /usr/lib/mission-core-node/insta360/prepare.py ]; then
/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/prepare.py --quiesce
fi
+231
View File
@@ -0,0 +1,231 @@
"""Idempotent fixed profile preparation. All Ubuntu prerequisites have an owner."""
import fcntl
import grp
import hashlib
import json
import os
import platform
import shutil
import subprocess
import sys
import time
import uuid
import zipfile
from contextlib import contextmanager
from pathlib import Path, PurePosixPath
sys.path.insert(0, str(Path(__file__).resolve().parent))
from layout import SHARE, STATE, directory, manifest, trusted, write # noqa: E402
from runtime.http import request # noqa: E402
STEPS = [
("platform", "Проверка совместимости системы"),
("payload", "Проверка встроенного драйвера"),
("runtime", "Развёртывание драйвера"),
("access", "Настройка доступа к камере"),
("service", "Запуск службы камеры"),
]
SOCKET = Path("/run/mission-core-insta360/driver.sock")
INSTANCES = Path("/run/mission-core-x4-instances")
def run(*args):
subprocess.run(args, check=True, timeout=45, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
def assert_safe():
if SOCKET.exists():
if request(SOCKET, "/prepare-safe", timeout=5).get("safe") is not True:
raise RuntimeError("Остановите просмотр и запись X4 перед обновлением драйвера.")
elif list(INSTANCES.glob("instax4_*")):
raise RuntimeError("Состояние X4 неизвестно. Подготовка не изменяла службы камеры.")
def entries(archive, bundle):
expected = bundle["files"]
if len(archive.infolist()) != len(expected) or set(archive.namelist()) != set(expected):
raise RuntimeError("Состав встроенного драйвера изменён.")
for name, info in expected.items():
path = PurePosixPath(name)
entry = archive.getinfo(name)
if (
path.is_absolute()
or ".." in path.parts
or path.as_posix() != name
or "\\" in name
or entry.is_dir()
or entry.file_size != info["bytes"]
or entry.file_size > 100 * 1024 * 1024
):
raise RuntimeError("Недопустимый файл драйвера.")
data = archive.read(entry)
if hashlib.sha256(data).hexdigest() != info["sha256"]:
raise RuntimeError("Контрольная сумма драйвера не совпала.")
yield name, data
def install_runtime(bundle):
parent = STATE / "runtime"
directory(parent)
ident = bundle["revision"]
if len(ident) != 24 or any(c not in "0123456789abcdef" for c in ident):
raise RuntimeError("Некорректная версия драйвера.")
target, stage = parent / ident, parent / (ident + ".partial")
source = trusted(SHARE / "payload.zip")
if hashlib.sha256(source.read_bytes()).hexdigest() != bundle["payload_sha256"]:
raise RuntimeError("Встроенный драйвер повреждён. Переустановите пакет.")
if stage.exists():
trusted(stage, True)
shutil.rmtree(stage)
if not target.exists():
directory(stage)
try:
with zipfile.ZipFile(source) as archive:
for name, data in entries(archive, bundle):
path = stage / name
path.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
path.write_bytes(data)
path.chmod(0o644)
stage.rename(target)
finally:
if stage.exists():
shutil.rmtree(stage)
trusted(target, True)
for name, info in bundle["files"].items():
path = target / name
for parent in path.parents:
if parent == target.parent:
break
trusted(parent, True)
data = trusted(path).read_bytes()
if len(data) != info["bytes"] or hashlib.sha256(data).hexdigest() != info["sha256"]:
raise RuntimeError("Установленный драйвер изменён. Нужна переустановка пакета.")
return ident
def prepare():
directory(STATE)
lock = STATE / "prepare.lock"
with lock.open("a") as handle:
trusted(lock)
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
with lifecycle_lock():
return prepare_locked()
@contextmanager
def lifecycle_lock():
directory(STATE)
path = STATE / "lifecycle.lock"
descriptor = os.open(path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o640)
with os.fdopen(descriptor, "r+b") as handle:
trusted(path)
os.fchown(handle.fileno(), 0, grp.getgrnam("mission-core-node").gr_gid)
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
yield
def prepare_locked():
bundle = manifest()
report = {
"schema": "missioncore.node.device-preparation/v1",
"model_id": "insta360.x4",
"revision": bundle["revision"],
"run_id": str(uuid.uuid4()),
"started_at": time.time(),
"monotonic_started": time.monotonic(),
"state": "running",
"steps": [{"id": key, "label": label, "state": "pending"} for key, label in STEPS],
}
def publish():
report["updated_at"] = time.time()
write(STATE / "preparation.json", (json.dumps(report, ensure_ascii=False) + "\n").encode())
publish()
try:
assert_safe()
for step in report["steps"]:
step["state"] = "running"
publish()
if step["id"] == "platform":
release = platform.freedesktop_os_release()
if (
release.get("ID"),
release.get("VERSION_ID"),
platform.machine(),
sys.version_info[:2],
) != ("ubuntu", "24.04", "x86_64", (3, 12)):
raise RuntimeError("Этот пакет поддерживает Ubuntu 24.04 amd64.")
elif step["id"] == "payload":
data = trusted(SHARE / "payload.zip").read_bytes()
if hashlib.sha256(data).hexdigest() != bundle["payload_sha256"]:
raise RuntimeError("Встроенный драйвер повреждён.")
elif step["id"] == "runtime":
ident = install_runtime(bundle)
assert_safe()
write(STATE / "active.path", (ident + "\n").encode())
elif step["id"] == "access":
run("/usr/bin/udevadm", "control", "--reload-rules")
run(
"/usr/bin/udevadm",
"trigger",
"--action=change",
"--subsystem-match=usb",
"--attr-match=idVendor=2e1a",
"--attr-match=idProduct=0002",
"--attr-match=product=Insta360 X4",
)
run("/usr/bin/udevadm", "settle", "--timeout=10")
elif step["id"] == "service":
maintenance = STATE / "maintenance"
if maintenance.exists():
trusted(maintenance).unlink()
run("/usr/bin/systemctl", "daemon-reload")
for service in (
"mission-core-insta360.service",
"mission-core-insta360-supervisor.service",
):
run("/usr/bin/systemctl", "enable", service)
run("/usr/bin/systemctl", "start", service)
run("/usr/bin/systemctl", "is-active", "--quiet", service)
step["state"] = "complete"
publish()
report["state"] = "complete"
except (
OSError,
ValueError,
RuntimeError,
subprocess.SubprocessError,
zipfile.BadZipFile,
) as error:
report["state"] = "error"
message = (
str(error)
if isinstance(error, RuntimeError)
else "Не удалось подготовить X4. Повторите действие."
)
report["message"] = message[:300]
for step in report["steps"]:
if step["state"] == "running":
step.update(state="error", message=message[:300])
elif step["state"] == "pending":
step["state"] = "blocked"
report["duration_seconds"] = time.monotonic() - report["monotonic_started"]
publish()
return report["state"] == "complete"
if __name__ == "__main__":
os.umask(0o022)
if os.geteuid() or sys.argv[1:] not in ([], ["--assert-safe"], ["--quiesce"]):
sys.exit(1)
if sys.argv[1:] == ["--quiesce"]:
with lifecycle_lock():
assert_safe()
write(STATE / "maintenance", b"package-lifecycle\n")
elif sys.argv[1:]:
assert_safe()
else:
sys.exit(0 if prepare() else 1)
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
set -eu
case "$1" in
remove|upgrade|deconfigure)
/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/prepare.py --quiesce
if [ -d /run/systemd/system ]; then
systemctl stop mission-core-insta360-supervisor.service
systemctl stop 'mission-core-x4@*.service'
systemctl stop mission-core-insta360.service
fi
;;
esac
@@ -0,0 +1,137 @@
{
"schema": "missioncore.insta360.python-lock/v1",
"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": "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": "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
}
]
}
+114
View File
@@ -0,0 +1,114 @@
{
"schema": "missioncore.insta360.sdk-source/v1",
"sdk_version": "2.1.8",
"repository": "https://github.com/pdxmusic/insta360sdk",
"commit": "3db9641ba612c639db10591d1402231662a1eb5d",
"directory": "CameraSDK-2.1.8-20260828_171805-linux-x86_64",
"source_kind": "public-third-party-mirror",
"vendor_authenticity": "not-independently-confirmed",
"redistribution_terms": "not-present-in-inspected-sdk-files",
"files": [
{
"path": "bin/jsons/camera_conf_Insta360_ONE_X2.json",
"bytes": 10160,
"sha256": "058a31c1cc15539b4466452caa6f653c8ad9255fd4a7681d2877fe87463c7260",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_One2.json",
"bytes": 8952,
"sha256": "76b0c9b9c331237ce69129c91629345ff91c5658e7c06cf3842cbf72556dab43",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_OneR.json",
"bytes": 12177,
"sha256": "0b74225f146aeda161a68eddae63e83bc9012989f533d935166e22044474095a",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_OneRS_283.json",
"bytes": 12297,
"sha256": "7260649cb20caf3f42ed604220f4c0209db41a4ecbf7cd82350d38cbc2c1fd47",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_OneRS_577.json",
"bytes": 12177,
"sha256": "0b74225f146aeda161a68eddae63e83bc9012989f533d935166e22044474095a",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_X3.json",
"bytes": 14391,
"sha256": "cf756d9155e3d4829e66f34e81d71731913366a4f0aa3677323c9663f112039c",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_X4.json",
"bytes": 145665,
"sha256": "b88b647847096dcb012f21255e3dfeaff15dc6f701f257c89c484337ecdb2104",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_X4_Air.json",
"bytes": 115749,
"sha256": "da0f7048c3125e1e1e11b20e641f04db4b65e9a23b51558a9ea85f99cff4097b",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_X5.json",
"bytes": 152752,
"sha256": "1a7a016439fca88928ad86844e7b4902b3153d9f2553719eb4a81aed98d314c7",
"lfs": false
},
{
"path": "bin/jsons/camera_conf_Insta360_X6.json",
"bytes": 237283,
"sha256": "9325937d5c4a60555f4c556c2e0f69ac0b89012232e4a6022f50c29bbd308aa0",
"lfs": false
},
{
"path": "include/camera/camera.h",
"bytes": 19842,
"sha256": "1e39bb9923d9ad87173bcb566936b7a84858d63f585a6b7d00046af4225498cf",
"lfs": false
},
{
"path": "include/camera/device_discovery.h",
"bytes": 1080,
"sha256": "55a03fa3597b2f913b112a4860ac7311f0620d7d01b1a75c6150779178d8b97c",
"lfs": false
},
{
"path": "include/camera/ins_types.h",
"bytes": 5960,
"sha256": "df13cb17a4972aec2cb05aa01383eb1c0d58d47d9379b9fc53940da581a98eac",
"lfs": false
},
{
"path": "include/camera/photography_settings.h",
"bytes": 20377,
"sha256": "f19047dde6a7903b9de8099685a92200caba3b279e9308f7563990e3db0b19b5",
"lfs": false
},
{
"path": "include/stream/stream_delegate.h",
"bytes": 1425,
"sha256": "115f757427a1c8542bcc912aeb10550615a8c538f7c37d0813fa9565d524b465",
"lfs": false
},
{
"path": "include/stream/stream_types.h",
"bytes": 436,
"sha256": "2791062f6e45603b47f9b26390506c2b49965efcafa7f5748cb71e89a49baaf9",
"lfs": false
},
{
"path": "lib/libCameraSDK.so",
"bytes": 17031504,
"sha256": "6d20aca1930101293308c056cef552c0beb8cbf1d6c7d567a79e95a52f9b1373",
"lfs": true
}
]
}
+157
View File
@@ -0,0 +1,157 @@
"""Root-only USB-to-unit reconciler. No client commands, SDK loading or network."""
import json
import os
import re
import signal
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from layout import CONTROL, active, directory, trusted, write # noqa: E402
from runtime.identity import read_binding # noqa: E402
UNITS = Path("/run/systemd/system")
PREFIX = "mission-core-x4@"
PATTERN = re.compile(r"instax4_[0-9a-f]{32}")
def systemctl(*args):
subprocess.run(
["/usr/bin/systemctl", *args],
check=True,
timeout=20,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
)
def unit(binding):
runtime = trusted(active(), True)
# Every interpolated value is a verified OS binding or root-owned revision.
return f"""[Unit]
Description=Mission Core isolated Insta360 X4
After=mission-core-insta360-supervisor.service
[Service]
Type=exec
DynamicUser=yes
User=mcx4-{binding.device_id[-20:]}
Group=mission-core-node
SupplementaryGroups=mission-core-x4-usb
ExecStart=/usr/bin/python3 -I /usr/lib/mission-core-node/insta360/bootstrap.py worker {binding.port}
WorkingDirectory={runtime}/bin
StateDirectory=mission-core-x4/{binding.device_id}
StateDirectoryMode=0700
RuntimeDirectory=mission-core-x4-instances/{binding.device_id}
RuntimeDirectoryMode=0750
UMask=0007
PrivateDevices=yes
BindPaths={binding.device_path}
DevicePolicy=closed
DeviceAllow={binding.device_path} rw
PrivateNetwork=yes
NoNewPrivileges=yes
CapabilityBoundingSet=
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictSUIDSGID=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
LockPersonality=yes
SystemCallFilter=~@mount
ReadOnlyPaths={CONTROL}
TasksMax=64
MemoryMax=384M
CPUQuota=150%
LimitNOFILE=256
LimitCORE=0
TimeoutStopSec=8
KillMode=control-group
Restart=no
StandardOutput=null
StandardError=null
""".encode()
def bindings():
found, duplicates = {}, set()
for path in Path("/sys/bus/usb/devices").iterdir():
try:
value = read_binding(path.name)
except (OSError, ValueError):
continue
if value.device_id in found:
duplicates.add(value.device_id)
found[value.device_id] = value
return {ident: value for ident, value in found.items() if ident not in duplicates}
def main():
if os.geteuid() or len(sys.argv) != 1:
raise RuntimeError("Use the fixed installed supervisor unit")
directory(CONTROL)
# Captured by root; a worker need not gain ptrace access to PID 1 merely
# to prove it uses a different network namespace.
write(CONTROL / "host-net-inode", str(Path("/proc/self/ns/net").stat().st_ino).encode())
stopping = False
def stop(*_):
nonlocal stopping
stopping = True
signal.signal(signal.SIGTERM, stop)
signal.signal(signal.SIGINT, stop)
known = {}
startup = True
record = CONTROL / "bindings.json"
if record.exists():
previous = json.loads(trusted(record).read_text())
for ident, info in previous.items():
if not PATTERN.fullmatch(ident):
raise RuntimeError("Invalid previous camera binding")
known[ident] = info
while not stopping:
observed = bindings()
current = {
ident: {
"port": item.port,
"bus": item.bus,
"address": item.address,
"revision": active().name,
}
for ident, item in observed.items()
}
changed = False
for ident in list(known):
if current.get(ident) != known[ident]:
systemctl("stop", PREFIX + ident + ".service")
path = UNITS / (PREFIX + ident + ".service")
if path.exists():
trusted(path).unlink()
del known[ident]
changed = True
additions = [ident for ident in current if ident not in known]
for ident in additions:
item = observed[ident]
if read_binding(item.port) != item:
continue
write(UNITS / (PREFIX + ident + ".service"), unit(item))
known[ident] = current[ident]
changed = True
if changed:
write(record, json.dumps(known, sort_keys=True).encode())
systemctl("daemon-reload")
for ident in list(current) if startup else additions:
if ident in known:
systemctl("start", PREFIX + ident + ".service")
startup = False
time.sleep(1)
if __name__ == "__main__":
main()