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
+145
View File
@@ -0,0 +1,145 @@
"""Bounded private HTTP over Unix sockets, never a public device endpoint."""
import http.client
import json
import socket
import socketserver
import struct
import threading
from dataclasses import dataclass
from http.server import BaseHTTPRequestHandler
LIMIT = 65536
INVENTORY_LIMIT = 2 * 1024 * 1024
@dataclass(frozen=True)
class Binary:
data: bytes
def request(path, route, value=None, timeout=25, binary=False):
client = http.client.HTTPConnection("driver", timeout=timeout)
client.sock = socket.socket(socket.AF_UNIX)
client.sock.settimeout(timeout)
try:
client.sock.connect(str(path))
body = None if value is None else json.dumps(value, allow_nan=False).encode()
client.request(
"GET" if value is None else "POST", route, body, {"Content-Type": "application/json"}
)
reply = client.getresponse()
limit = (
4 * 1024 * 1024 + 1028
if binary
else INVENTORY_LIMIT
if route == "/inventory"
else LIMIT
)
raw = reply.read(limit + 1)
if len(raw) > limit or reply.status != 200:
raise RuntimeError("Camera service request failed")
if binary:
if reply.getheader("Content-Type") != "application/octet-stream":
raise RuntimeError("Invalid video response")
return raw
return json.loads(raw)
except http.client.HTTPException as error:
raise RuntimeError("Camera service response is incomplete") from error
finally:
client.close()
class Server(socketserver.ThreadingMixIn, socketserver.UnixStreamServer):
daemon_threads = True
block_on_close = False
def __init__(self, path, dispatch, allowed_uids):
self.dispatch = dispatch
self.allowed_uids = frozenset(allowed_uids)
self.slots = threading.BoundedSemaphore(12)
super().__init__(str(path), Handler)
def verify_request(self, request, address):
_, uid, _ = struct.unpack(
"3i", request.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, 12)
)
return uid in self.allowed_uids
def process_request(self, request, address):
if not self.slots.acquire(blocking=False):
self.shutdown_request(request)
return
try:
super().process_request(request, address)
except BaseException:
self.slots.release()
raise
def process_request_thread(self, request, address):
try:
super().process_request_thread(request, address)
finally:
self.slots.release()
class Handler(BaseHTTPRequestHandler):
def setup(self):
self.request.settimeout(10)
super().setup()
def log_message(self, *_):
pass
def do_GET(self):
self.handle_request()
def do_POST(self):
self.handle_request()
def handle_request(self):
try:
if self.headers.get("Transfer-Encoding"):
raise ValueError("Chunked commands are not supported")
value = None
if self.command == "POST":
length = int(self.headers.get("Content-Length", "0"))
if (
not 0 < length <= LIMIT
or self.headers.get("Content-Type") != "application/json"
):
raise ValueError("Invalid command size")
raw = self.rfile.read(length)
if len(raw) != length:
raise ValueError("Truncated command")
value = json.loads(raw)
result = self.server.dispatch(self.command, self.path, value, self.headers)
status = 200
except (ValueError, KeyError, TypeError):
status, result = 400, {"error": "Некорректная команда камеры."}
except (RuntimeError, OSError, TimeoutError):
status, result = 409, {"error": "Камера не ответила. Проверьте подключение."}
binary = isinstance(result, Binary)
data = (
result.data
if binary
else json.dumps(result, ensure_ascii=False, allow_nan=False).encode()
)
limit = (
4 * 1024 * 1024 + 1028
if binary
else INVENTORY_LIMIT
if self.path == "/inventory"
else LIMIT
)
if len(data) > limit:
binary = False
status, data = 500, b'{"error":"Camera response exceeds its limit"}'
self.send_response(status)
self.send_header(
"Content-Type", "application/octet-stream" if binary else "application/json"
)
self.send_header("Content-Length", str(len(data)))
self.send_header("Connection", "close")
self.end_headers()
self.wfile.write(data)