#!/usr/bin/env python3
"""Canonical data-only deploy runner for the dedicated NODE.DC Device Edge."""

from __future__ import annotations

import hashlib
import json
import os
import re
import select
import shutil
import socket
import struct
import subprocess
import sys
import tarfile
import tempfile
import time
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath


RUNNER_PATH = Path("/usr/local/sbin/nodedc-edge-deploy")
LIVE_ROOT = Path("/home/ndcsudo/nodedc-device-edge/source")
INBOX_ROOT = Path("/home/ndcsudo/nodedc-device-edge/deploy/inbox")
STATE_ROOT = Path("/var/lib/nodedc-edge-deploy")
APPLIED_ROOT = STATE_ROOT / "applied"
FAILED_ROOT = STATE_ROOT / "failed"
BACKUP_ROOT = STATE_ROOT / "backups"
APPLIED_JOURNAL = STATE_ROOT / "state/applied.jsonl"
FAILED_JOURNAL = STATE_ROOT / "state/failed.jsonl"
DEPLOY_LOCK = STATE_ROOT / "state/deploy.lock"

DOCKER = "/usr/bin/docker"
COMPONENT = "device-edge"
ARTIFACT_TYPE = "app-overlay"
PATCH_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
MAX_ARTIFACT_BYTES = 16 * 1024 * 1024

COMPOSE_PROJECT = "nodedc-device-edge"
BASE_COMPOSE = LIVE_ROOT / "docker-compose.device-edge.yml"
INGRESS_COMPOSE = LIVE_ROOT / "docker-compose.device-edge.ingress.yml"
RELAY_SERVICE = "device-edge-relay"
RELAY_CONTAINER = "nodedc-device-edge-device-edge-relay-1"
BACKHAUL_CONTAINER = "nodedc-device-edge-device-edge-backhaul-1"
TAILNET_CONTAINER = "nodedc-device-edge-tailnet-1"
RELAY_IMAGE = "nodedc/device-edge-relay:local"

INGRESS_PARENT = "enp1s0f0"
INGRESS_SUBNET = "192.168.68.0/22"
INGRESS_GATEWAY = "192.168.68.1"
INGRESS_IPV4 = "192.168.71.253"
INGRESS_PORT = 9921
INGRESS_NETWORK = "nodedc-device-edge-ingress"
INGRESS_IPV4_APPROVED = True
INGRESS_IPV4_APPROVAL = "approved-outside-dhcp-pool"

ENTRIES = (
    "docker-compose.device-edge.yml",
    "docker-compose.device-edge.ingress.yml",
    "services/device-edge-relay/Dockerfile",
    "services/device-edge-relay/src",
    "deployment/device-edge-admission-gate-v1.json",
)

PAYLOAD_FILE_SHA256 = {
    "docker-compose.device-edge.yml":
        "666945ffd9512355e610ecd36a9df96936477315150555def93e0243e8ff1e22",
    "docker-compose.device-edge.ingress.yml":
        "11bedfd7fdea749ca1bdb3b35b9c136c86b330f51a9001f0b38c4618f6f96108",
    "services/device-edge-relay/Dockerfile":
        "f2f15b7618ac2ab3a1d4dd40041d06d1e9a695edcfc168d8835f9560b4897e70",
    "services/device-edge-relay/src/runtime.mjs":
        "21e83678980aa61127bf9f3d77982dd485c4aaae208c43818db7bb1cc150b83a",
    "services/device-edge-relay/src/server.mjs":
        "1b99ec944f1d3fbadded045b159f08624e2829620cec39a97f6b4b8cdcd2be22",
    "deployment/device-edge-admission-gate-v1.json":
        "e6c1f21ff297b451c42b6746bc2063484874435dfa9f1614410a7cbe84f0ce6f",
}

PREDECESSOR_FILE_SHA256 = {
    "docker-compose.device-edge.yml":
        "7f13c11d6d4d541964053c0a8cf791e401947d34c42e0f7c26f9f9df26fa00b5",
    "docker-compose.device-edge.ingress.yml":
        "a4afd04755530fc3b9be64d1a65f0f7282a9539bcc1985bfd880aa904e1c4d8f",
    "services/device-edge-relay/Dockerfile":
        "f2f15b7618ac2ab3a1d4dd40041d06d1e9a695edcfc168d8835f9560b4897e70",
    "services/device-edge-relay/src/runtime.mjs":
        "ae8bf8b55603bab266b6fa6e9bc65c9f310a9d94a54db04e2130704e38622ffc",
    "services/device-edge-relay/src/server.mjs":
        "e4b051b74f934bd37322440e6a013fb6774a76607da08f9cc1e844fc109c83c1",
    "deployment/device-edge-ingress-ipvlan-v1.json":
        "b9ce402db0c059a76f07a8d4a34297aff2250fd1c0d1aed9970b3a88f4e75d7f",
}

PREDECESSOR_ABSENT = {
    "deployment/device-edge-admission-gate-v1.json",
}


class DeployError(RuntimeError):
    pass


def die(message: str) -> None:
    raise DeployError(message)


def sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def run(command, *, check=True, capture=True, cwd=None, timeout=180):
    result = subprocess.run(
        [str(value) for value in command],
        check=False,
        capture_output=capture,
        text=True,
        cwd=str(cwd) if cwd else None,
        timeout=timeout,
    )
    if check and result.returncode != 0:
        detail = (result.stderr or result.stdout or "command failed").strip()
        die(f"command failed: {command[0]}: {detail}")
    return result


def docker_json(*args):
    result = run([DOCKER, *args])
    try:
        return json.loads(result.stdout)
    except json.JSONDecodeError as error:
        die(f"Docker JSON response invalid: {error}")


def expected_descriptor():
    return {
        "schemaVersion": "nodedc.device-edge.admission-gate.v1",
        "mode": "single-nic-ipvlan-b2-relay-only",
        "runtimeHost": "ndcmini12",
        "component": COMPONENT,
        "selectedServices": [RELAY_SERVICE],
        "preservedServices": ["device-edge-backhaul", "tailnet"],
        "composeProject": COMPOSE_PROJECT,
        "composeFiles": [
            "docker-compose.device-edge.yml",
            "docker-compose.device-edge.ingress.yml",
        ],
        "parentInterface": INGRESS_PARENT,
        "lanSubnet": INGRESS_SUBNET,
        "lanGateway": INGRESS_GATEWAY,
        "ingressIpv4": INGRESS_IPV4,
        "ingressIpv4Approval": INGRESS_IPV4_APPROVAL,
        "ingressNetwork": INGRESS_NETWORK,
        "deviceTcpListen": f"{INGRESS_IPV4}:{INGRESS_PORT}",
        "hostPortPublication": "disabled",
        "healthPublication": "disabled",
        "privateUpstream": "device-edge-backhaul:19921",
        "sourceAdmission": "public-ipv4-only",
        "maxTrackedSourceAddresses": 2048,
        "maxBytesPerDirection": 262144,
        "protocolInspection": "gateway-owned",
        "identityTrust": "claimed-not-ownership-proof",
        "discoveryLifecycle": "quarantine",
        "commandTransport": "disabled",
        "gelios": "untouched",
        "amneziaHostFullTunnel": "preserved",
        "routerNatFirewall": "separate-manual-gate",
        "rollback": "restore-reviewed-ipvlan-predecessor-without-network-or-router-mutation",
    }


def assert_root():
    if os.geteuid() != 0:
        die("nodedc-edge-deploy must run as root")


def assert_regular_nonsymlink(path: Path, label: str):
    if not path.exists() or path.is_symlink() or not path.is_file():
        die(f"{label} must be a regular non-symlink file")


def parse_manifest(raw: str):
    values = {}
    for line in raw.splitlines():
        if not line or "=" not in line:
            die("artifact manifest is malformed")
        key, value = line.split("=", 1)
        if key in values or key not in {"id", "component", "type"}:
            die("artifact manifest key set is invalid")
        values[key] = value
    if set(values) != {"id", "component", "type"}:
        die("artifact manifest key set is incomplete")
    if not PATCH_ID_RE.fullmatch(values["id"]):
        die("artifact patch id is invalid")
    if values["component"] != COMPONENT or values["type"] != ARTIFACT_TYPE:
        die("artifact component/type mismatch")
    return values


def safe_tar_member(member: tarfile.TarInfo):
    path = PurePosixPath(member.name)
    if path.is_absolute() or ".." in path.parts or not path.parts:
        die("artifact contains an unsafe path")
    if not (member.isfile() or member.isdir()):
        die("artifact contains a non-file/non-directory member")
    lowered = {part.lower() for part in path.parts}
    if any(
        part.startswith(".env")
        or part in {
            ".git",
            "node_modules",
            "secrets",
            "keys",
            "trust",
            "runtime",
            "logs",
            "uploads",
        }
        for part in lowered
    ):
        die("artifact contains a forbidden boundary")
    if any(part.startswith("._") for part in path.parts):
        die("artifact contains AppleDouble metadata")


def load_artifact(artifact: Path, extraction_root: Path):
    artifact = artifact.resolve(strict=True)
    if artifact.parent != INBOX_ROOT.resolve(strict=True):
        die("artifact must be an explicit file in the Device Edge inbox")
    assert_regular_nonsymlink(artifact, "artifact")
    if artifact.suffix != ".tgz" or artifact.stat().st_size > MAX_ARTIFACT_BYTES:
        die("artifact extension/size rejected")

    seen = set()
    with tarfile.open(artifact, "r:gz") as archive:
        for member in archive.getmembers():
            safe_tar_member(member)
            if member.name in seen:
                die("artifact contains duplicate members")
            seen.add(member.name)
        required = {"manifest.env", "files.txt", "payload"}
        if not required.issubset(seen):
            die("artifact top-level contract is incomplete")
        if any(name.split("/", 1)[0] not in required for name in seen):
            die("artifact contains an unexpected top-level member")
        archive.extractall(extraction_root, filter="data")

    manifest = parse_manifest(
        (extraction_root / "manifest.env").read_text(encoding="utf-8")
    )
    entries = tuple(
        line for line in
        (extraction_root / "files.txt").read_text(encoding="utf-8").splitlines()
        if line
    )
    if entries != ENTRIES or len(entries) != len(set(entries)):
        die("Device Edge artifact file selection mismatch")
    payload = extraction_root / "payload"
    validate_payload(payload)
    return manifest, entries, payload, sha256_file(artifact), artifact


def validate_payload(payload: Path):
    actual_files = {
        path.relative_to(payload).as_posix(): sha256_file(path)
        for path in payload.rglob("*")
        if path.is_file()
    }
    if actual_files != PAYLOAD_FILE_SHA256:
        die("Device Edge artifact payload digest set mismatch")
    descriptor = json.loads(
        (payload / "deployment/device-edge-admission-gate-v1.json")
        .read_text(encoding="utf-8")
    )
    if descriptor != expected_descriptor():
        die("Device Edge ingress descriptor mismatch")


def journal_records(path: Path):
    if not path.exists():
        return []
    records = []
    for line in path.read_text(encoding="utf-8").splitlines():
        if not line:
            continue
        try:
            records.append(json.loads(line))
        except json.JSONDecodeError:
            die(f"journal is malformed: {path}")
    return records


def assert_new_identity(patch_id: str, artifact_sha256: str):
    records = journal_records(APPLIED_JOURNAL) + journal_records(FAILED_JOURNAL)
    if any(record.get("patch") == patch_id for record in records):
        die("Device Edge patch id is terminally recorded")
    if any(record.get("sha256") == artifact_sha256 for record in records):
        die("Device Edge artifact digest is terminally recorded")


def current_source_state():
    state = {}
    for relative, expected in PREDECESSOR_FILE_SHA256.items():
        path = LIVE_ROOT / relative
        assert_regular_nonsymlink(path, f"predecessor {relative}")
        state[relative] = sha256_file(path)
        if state[relative] != expected:
            die(f"Device Edge predecessor drift: {relative}")
    for relative in PREDECESSOR_ABSENT:
        if (LIVE_ROOT / relative).exists():
            die(f"Device Edge predecessor unexpected path: {relative}")
    return state


def inspect_container(name: str):
    response = docker_json("inspect", name)
    if len(response) != 1:
        die(f"container inspect cardinality mismatch: {name}")
    return response[0]


def container_health(container):
    health = container.get("State", {}).get("Health")
    return health.get("Status") if health else None


def preserved_runtime_snapshot():
    snapshot = {}
    for name in (BACKHAUL_CONTAINER, TAILNET_CONTAINER):
        container = inspect_container(name)
        if container.get("State", {}).get("Status") != "running":
            die(f"preserved Device Edge service is not running: {name}")
        if name == BACKHAUL_CONTAINER and container_health(container) != "healthy":
            die("Device Edge backhaul is not healthy")
        snapshot[name] = {
            "Id": container.get("Id"),
            "Image": container.get("Image"),
            "StartedAt": container.get("State", {}).get("StartedAt"),
            "RestartCount": container.get("RestartCount"),
            "PortBindings": container.get("HostConfig", {}).get("PortBindings"),
        }
    return snapshot


def assert_preserved_runtime(snapshot):
    current_snapshot = preserved_runtime_snapshot()
    for name, expected in snapshot.items():
        current = current_snapshot[name]
        if current != expected:
            die(f"preserved Device Edge runtime changed: {name}")


def validate_predecessor_runtime():
    relay = inspect_container(RELAY_CONTAINER)
    if relay.get("State", {}).get("Status") != "running":
        die("Device Edge IPvlan predecessor relay is not running")
    if container_health(relay) != "healthy":
        die("Device Edge IPvlan predecessor relay is not healthy")
    environment = set(relay.get("Config", {}).get("Env") or [])
    required = {
        "DEVICE_EDGE_RELAY_HEALTH_HOST=127.0.0.1",
        "DEVICE_EDGE_RELAY_INGRESS_ENABLED=true",
        "DEVICE_EDGE_RELAY_TCP_HOST=0.0.0.0",
        "DEVICE_EDGE_RELAY_TCP_PORT=9921",
        "DEVICE_EDGE_RELAY_UPSTREAM_HOST=device-edge-backhaul",
        "DEVICE_EDGE_RELAY_UPSTREAM_PORT=19921",
    }
    if not required.issubset(environment):
        die("Device Edge IPvlan predecessor environment mismatch")
    bindings = relay.get("HostConfig", {}).get("PortBindings") or {}
    if bindings not in ({}, None):
        die("Device Edge IPvlan predecessor host publication mismatch")
    validate_network_runtime(relay)


def validate_host_network_boundary():
    if socket.gethostname() != "ndcmini12":
        die("Device Edge runtime host mismatch")
    route = run(["/usr/sbin/ip", "-4", "route", "show"]).stdout
    for line in (
        "0.0.0.0/1 dev amn0 metric 1",
        "128.0.0.0/1 dev amn0 metric 1",
        "default via 192.168.68.1 dev enp1s0f0",
        "192.168.68.0/22 dev enp1s0f0",
    ):
        if line not in route:
            die(f"Device Edge host route boundary mismatch: {line}")
    if run(["/usr/bin/systemctl", "is-active", "AmneziaVPN.service"]).stdout.strip() != "active":
        die("AmneziaVPN must remain active for this transition")
    interface = run([
        "/usr/sbin/ip", "-4", "-brief", "address", "show", "dev", INGRESS_PARENT,
    ]).stdout
    if "192.168.68.54/22" not in interface or "UP" not in interface:
        die("Device Edge physical interface boundary mismatch")


def arp_duplicate_detected(target_ip: str, interface: str, attempts=3):
    protocol = 0x0806
    raw = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(protocol))
    try:
        raw.bind((interface, 0))
        source_mac = raw.getsockname()[4]
        target = socket.inet_aton(target_ip)
        ethernet = b"\xff" * 6 + source_mac + struct.pack("!H", protocol)
        arp = struct.pack(
            "!HHBBH6s4s6s4s",
            1,
            0x0800,
            6,
            4,
            1,
            source_mac,
            b"\x00" * 4,
            b"\x00" * 6,
            target,
        )
        raw.setblocking(False)
        for _ in range(attempts):
            raw.send(ethernet + arp)
            deadline = time.monotonic() + 0.7
            while time.monotonic() < deadline:
                ready, _, _ = select.select([raw], [], [], deadline - time.monotonic())
                if not ready:
                    break
                packet = raw.recv(2048)
                if len(packet) < 42 or packet[12:14] != b"\x08\x06":
                    continue
                if packet[28:32] == target and packet[22:28] != source_mac:
                    return True
        return False
    finally:
        raw.close()


def preflight(manifest, artifact_sha256):
    if not INGRESS_IPV4_APPROVED:
        die("Device Edge ingress IPv4 approval is not granted")
    if INGRESS_IPV4_APPROVAL != "approved-outside-dhcp-pool":
        die("Device Edge ingress IPv4 approval contract mismatch")
    assert_new_identity(manifest["id"], artifact_sha256)
    current_source_state()
    validate_predecessor_runtime()
    preserved = preserved_runtime_snapshot()
    validate_host_network_boundary()
    return preserved


def compose_command(*args, baseline=False):
    command = [
        DOCKER,
        "compose",
        "--project-name",
        COMPOSE_PROJECT,
        "--file",
        str(BASE_COMPOSE),
    ]
    if not baseline:
        command.extend(["--file", str(INGRESS_COMPOSE)])
    command.extend(args)
    return command


def ensure_state_directories():
    for path in (
        APPLIED_ROOT,
        FAILED_ROOT,
        BACKUP_ROOT,
        APPLIED_JOURNAL.parent,
    ):
        path.mkdir(parents=True, exist_ok=True, mode=0o750)
        os.chmod(path, 0o750)


def acquire_lock():
    ensure_state_directories()
    try:
        descriptor = os.open(
            DEPLOY_LOCK,
            os.O_WRONLY | os.O_CREAT | os.O_EXCL,
            0o600,
        )
    except FileExistsError:
        die("Device Edge deploy lock is present")
    os.write(descriptor, f"pid={os.getpid()}\n".encode())
    os.close(descriptor)


def release_lock():
    try:
        DEPLOY_LOCK.unlink()
    except FileNotFoundError:
        pass


def create_backup(patch_id: str):
    timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
    backup_id = f"{patch_id}-{timestamp}"
    backup = BACKUP_ROOT / backup_id
    backup.mkdir(parents=False, mode=0o750)
    present = []
    absent = []
    for relative in ENTRIES:
        source = LIVE_ROOT / relative
        target = backup / "payload" / relative
        if not source.exists():
            absent.append(relative)
            continue
        present.append(relative)
        target.parent.mkdir(parents=True, exist_ok=True)
        if source.is_dir():
            shutil.copytree(source, target, symlinks=False)
        else:
            shutil.copy2(source, target, follow_symlinks=False)
    (backup / "backup.json").write_text(json.dumps({
        "schemaVersion": "nodedc.device-edge.backup.v1",
        "patch": patch_id,
        "present": present,
        "absent": absent,
    }, sort_keys=True, indent=2) + "\n", encoding="utf-8")
    return backup_id, backup


def publish_payload(payload: Path):
    for relative in ENTRIES:
        source = payload / relative
        target = LIVE_ROOT / relative
        if target.exists():
            if target.is_dir():
                shutil.rmtree(target)
            else:
                target.unlink()
        target.parent.mkdir(parents=True, exist_ok=True)
        if source.is_dir():
            shutil.copytree(source, target, symlinks=False)
        else:
            shutil.copy2(source, target, follow_symlinks=False)


def restore_backup(backup: Path):
    descriptor = json.loads((backup / "backup.json").read_text(encoding="utf-8"))
    for relative in ENTRIES:
        target = LIVE_ROOT / relative
        if target.exists():
            if target.is_dir():
                shutil.rmtree(target)
            else:
                target.unlink()
    for relative in descriptor["present"]:
        source = backup / "payload" / relative
        target = LIVE_ROOT / relative
        target.parent.mkdir(parents=True, exist_ok=True)
        if source.is_dir():
            shutil.copytree(source, target, symlinks=False)
        else:
            shutil.copy2(source, target, follow_symlinks=False)


def build_relay():
    run([
        DOCKER,
        "build",
        "--no-cache",
        "--network=host",
        "--file",
        "services/device-edge-relay/Dockerfile",
        "--tag",
        RELAY_IMAGE,
        ".",
    ], cwd=LIVE_ROOT, timeout=900, capture=False)


def wait_healthy(name: str, timeout_seconds=150):
    deadline = time.monotonic() + timeout_seconds
    while time.monotonic() < deadline:
        try:
            container = inspect_container(name)
        except DeployError:
            time.sleep(2)
            continue
        if (
            container.get("State", {}).get("Status") == "running"
            and container_health(container) == "healthy"
        ):
            return container
        if container.get("State", {}).get("Status") in {"exited", "dead"}:
            die(f"container stopped before health acceptance: {name}")
        time.sleep(2)
    die(f"container health timeout: {name}")


def validate_network_runtime(relay):
    networks = relay.get("NetworkSettings", {}).get("Networks") or {}
    if set(networks) != {"nodedc-device-edge-private", INGRESS_NETWORK}:
        die("Device Edge relay network set mismatch")
    if networks[INGRESS_NETWORK].get("IPAddress") != INGRESS_IPV4:
        die("Device Edge relay IPvlan address mismatch")
    response = docker_json("network", "inspect", INGRESS_NETWORK)
    if len(response) != 1:
        die("Device Edge ingress network cardinality mismatch")
    network = response[0]
    if network.get("Driver") != "ipvlan" or network.get("Internal") is True:
        die("Device Edge ingress network driver mismatch")
    options = network.get("Options") or {}
    if options.get("parent") != INGRESS_PARENT or options.get("ipvlan_mode") != "l2":
        die("Device Edge ingress network option mismatch")
    configs = network.get("IPAM", {}).get("Config") or []
    if len(configs) != 1:
        die("Device Edge ingress IPAM cardinality mismatch")
    if configs[0].get("Subnet") != INGRESS_SUBNET or configs[0].get("Gateway") != INGRESS_GATEWAY:
        die("Device Edge ingress IPAM mismatch")


def validate_relay_runtime(preserved):
    relay = wait_healthy(RELAY_CONTAINER)
    if relay.get("Config", {}).get("User") != "1000:1000":
        die("Device Edge relay user mismatch")
    host = relay.get("HostConfig", {})
    if host.get("ReadonlyRootfs") is not True or host.get("Privileged") is not False:
        die("Device Edge relay filesystem/privilege mismatch")
    if set(host.get("CapDrop") or []) != {"ALL"}:
        die("Device Edge relay capability mismatch")
    if host.get("PortBindings") not in ({}, None):
        die("Device Edge relay host port publication detected")
    environment = set(relay.get("Config", {}).get("Env") or [])
    required = {
        "DEVICE_EDGE_RELAY_HEALTH_HOST=127.0.0.1",
        "DEVICE_EDGE_RELAY_INGRESS_ENABLED=true",
        "DEVICE_EDGE_RELAY_TCP_HOST=0.0.0.0",
        "DEVICE_EDGE_RELAY_TCP_PORT=9921",
        "DEVICE_EDGE_RELAY_UPSTREAM_HOST=device-edge-backhaul",
        "DEVICE_EDGE_RELAY_UPSTREAM_PORT=19921",
        "DEVICE_EDGE_RELAY_SOURCE_POLICY=public-ipv4-only",
        "DEVICE_EDGE_RELAY_MAX_TRACKED_SOURCE_ADDRESSES=2048",
        "DEVICE_EDGE_RELAY_MAX_BYTES_PER_DIRECTION=262144",
    }
    if not required.issubset(environment):
        die("Device Edge relay environment mismatch")
    validate_network_runtime(relay)
    health_result = run([
        DOCKER,
        "exec",
        RELAY_CONTAINER,
        "node",
        "-e",
        "fetch('http://127.0.0.1:18221/healthz').then(async r=>{if(!r.ok)process.exit(2);console.log(await r.text())}).catch(()=>process.exit(3))",
    ])
    try:
        health = json.loads(health_result.stdout)
    except json.JSONDecodeError:
        die("Device Edge relay health JSON invalid")
    expected_health = {
        "ok": True,
        "service": "nodedc-device-edge-relay",
        "ingress": "relay-only",
        "protocolInspection": "disabled",
        "commandTransport": "disabled",
        "sourceAdmission": "public-ipv4-only",
    }
    for key, expected in expected_health.items():
        if health.get(key) != expected:
            die(f"Device Edge relay health contract mismatch: {key}")
    run([
        DOCKER,
        "exec",
        RELAY_CONTAINER,
        "node",
        "-e",
        "const n=require('node:net');const s=n.connect({host:'device-edge-backhaul',port:19921});s.setTimeout(5000);s.once('connect',()=>{s.destroy();process.exit(0)});s.once('timeout',()=>process.exit(2));s.once('error',()=>process.exit(3))",
    ])
    validate_host_network_boundary()
    assert_preserved_runtime(preserved)


def write_journal(path: Path, record):
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps(record, sort_keys=True) + "\n")


def archive_artifact(artifact: Path, destination_root: Path):
    destination = destination_root / artifact.name
    if destination.exists():
        die("Device Edge artifact archive collision")
    os.replace(artifact, destination)
    return destination


def rollback(backup: Path, preserved):
    restore_backup(backup)
    run(compose_command(
        "up",
        "--detach",
        "--no-deps",
        "--force-recreate",
        "--pull",
        "never",
        RELAY_SERVICE,
    ), cwd=LIVE_ROOT, timeout=300, capture=False)
    wait_healthy(RELAY_CONTAINER)
    current_source_state()
    validate_predecessor_runtime()
    assert_preserved_runtime(preserved)


def plan_artifact(artifact_argument: str):
    assert_root()
    artifact = Path(artifact_argument)
    with tempfile.TemporaryDirectory(prefix="nodedc-edge-plan-") as directory:
        manifest, entries, _payload, digest, resolved = load_artifact(
            artifact,
            Path(directory),
        )
        preflight(manifest, digest)
    print("== plan ==")
    print(f"artifact={resolved.name}")
    print(f"sha256={digest}")
    print(f"id={manifest['id']}")
    print(f"component={COMPONENT}")
    print(f"type={ARTIFACT_TYPE}")
    print(f"payload_root={LIVE_ROOT}")
    print(f"compose_root={LIVE_ROOT}")
    print(f"compose_project={COMPOSE_PROJECT}")
    print("compose_files=docker-compose.device-edge.yml docker-compose.device-edge.ingress.yml")
    print("build=/usr/bin/docker build --no-cache --network=host -f services/device-edge-relay/Dockerfile -t nodedc/device-edge-relay:local .")
    print("services=device-edge-relay")
    print("preserved_services=device-edge-backhaul tailnet")
    print(f"device_edge_ingress=ipvlan:l2:{INGRESS_PARENT}:{INGRESS_IPV4}:{INGRESS_PORT}/tcp")
    print(f"device_edge_lan={INGRESS_SUBNET}:gateway:{INGRESS_GATEWAY}")
    print(f"device_edge_ingress_ipv4_approval={INGRESS_IPV4_APPROVAL}")
    print("device_edge_host_port_publication=disabled")
    print("device_edge_health_publication=disabled")
    print("device_edge_private_upstream=device-edge-backhaul:19921")
    print("device_edge_source_admission=public-ipv4-only")
    print("device_edge_source_table_limit=2048")
    print("device_edge_byte_limit_per_direction=262144")
    print("device_edge_command_transport=disabled")
    print("device_edge_discovery_lifecycle=quarantine")
    print("device_edge_gelios=untouched")
    print("device_edge_amnezia=preserved:active:host-full-tunnel")
    print("device_edge_router_nat_firewall=unchanged")
    print("device_edge_rollback=restore-reviewed-ipvlan-predecessor-no-router-mutation")
    print("state=new")
    print("== files ==")
    for entry in entries:
        print(f"  {entry}")


def apply_artifact(artifact_argument: str):
    assert_root()
    artifact = Path(artifact_argument)
    acquire_lock()
    manifest = None
    digest = None
    resolved = None
    backup_id = None
    backup = None
    preserved = None
    try:
        with tempfile.TemporaryDirectory(prefix="nodedc-edge-apply-") as directory:
            manifest, _entries, payload, digest, resolved = load_artifact(
                artifact,
                Path(directory),
            )
            preserved = preflight(manifest, digest)
            backup_id, backup = create_backup(manifest["id"])
            publish_payload(payload)
            build_relay()
            run(compose_command(
                "up",
                "--detach",
                "--no-deps",
                "--force-recreate",
                "--pull",
                "never",
                RELAY_SERVICE,
            ), cwd=LIVE_ROOT, timeout=300, capture=False)
            validate_relay_runtime(preserved)
        archived = archive_artifact(resolved, APPLIED_ROOT)
        write_journal(APPLIED_JOURNAL, {
            "status": "ok",
            "patch": manifest["id"],
            "component": COMPONENT,
            "sha256": digest,
            "artifact": archived.name,
            "backup": backup_id,
            "appliedAt": datetime.now(timezone.utc).isoformat(),
        })
        print(
            f"deploy-ok patch={manifest['id']} component={COMPONENT} "
            f"backup={backup_id}"
        )
    except Exception as error:
        rollback_status = "not-started"
        if backup is not None and preserved is not None:
            try:
                rollback(backup, preserved)
                rollback_status = "ok"
            except Exception as rollback_error:
                rollback_status = f"failed:{type(rollback_error).__name__}"
        if resolved is not None and resolved.exists():
            failed_name = (
                FAILED_ROOT
                / f"{resolved.name}.{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
            )
            os.replace(resolved, failed_name)
        if manifest is not None and digest is not None:
            write_journal(FAILED_JOURNAL, {
                "status": "failed",
                "patch": manifest["id"],
                "component": COMPONENT,
                "sha256": digest,
                "backup": backup_id,
                "rollback": rollback_status,
                "error": type(error).__name__,
                "failedAt": datetime.now(timezone.utc).isoformat(),
            })
        if rollback_status.startswith("failed"):
            die(f"apply failed and rollback failed: {error}")
        die(f"apply failed; automatic rollback={rollback_status}: {error}")
    finally:
        release_lock()


def verify_install():
    assert_root()
    path = RUNNER_PATH if RUNNER_PATH.exists() else Path(__file__).resolve()
    assert_regular_nonsymlink(path, "runner")
    docker_version = run([DOCKER, "version", "--format", "{{.Server.Version}}"]).stdout.strip()
    compose_version = run([DOCKER, "compose", "version", "--short"]).stdout.strip()
    print(f"path={path}")
    print(f"sha256={sha256_file(path)}")
    print(f"python={sys.version.split()[0]}")
    print(f"docker={docker_version}")
    print(f"compose={compose_version}")
    print(f"device_edge_ingress_ipv4={INGRESS_IPV4}")
    print(f"device_edge_ingress_ipv4_approval={INGRESS_IPV4_APPROVAL}")
    print("device_edge_source_admission=public-ipv4-only")
    print("verify-install-ok")


def main(arguments):
    if len(arguments) == 1 and arguments[0] == "verify-install":
        verify_install()
        return 0
    if len(arguments) == 2 and arguments[0] == "plan":
        plan_artifact(arguments[1])
        return 0
    if len(arguments) == 2 and arguments[0] == "apply":
        apply_artifact(arguments[1])
        return 0
    print(
        "usage: nodedc-edge-deploy verify-install | plan <artifact.tgz> | apply <artifact.tgz>",
        file=sys.stderr,
    )
    return 2


if __name__ == "__main__":
    try:
        raise SystemExit(main(sys.argv[1:]))
    except DeployError as error:
        print(f"ERROR: {error}", file=sys.stderr)
        raise SystemExit(1)
