{ lib, config, pkgs, ... }:

let
  cfg = config.services.every-channel.ec-node;

  ecNodePkgDefault = pkgs.callPackage ../pkgs/ec-node.nix { };
  ecCliPkgDefault = pkgs.callPackage ../pkgs/ec-cli.nix { };
  nodeEntrypoint = pkgs.callPackage ../pkgs/every-channel-node.nix {
    ec-node = cfg.package;
    moq-relay = null;
  };

  # Minimal normalization for host strings.
  normalizeHost = host:
    let
      h = lib.strings.removeSuffix "/" host;
    in
    if lib.strings.hasPrefix "http://" h || lib.strings.hasPrefix "https://" h then h else "http://${h}";

  sanitizeUnitName = s:
    lib.concatStringsSep "-" (lib.filter (x: x != "") (lib.splitString "/" (lib.replaceStrings [ " " ":" "." ] [ "-" "-" "-" ] s)));

  archiveConvergenceProofUnitName = proof:
    "every-channel-archive-convergence-${sanitizeUnitName proof.name}";
  archiveConvergenceMeasureProofUnitName = proof:
    "every-channel-archive-convergence-measure-${sanitizeUnitName proof.name}";

  hdhrBase = if cfg.hdhomerun.host != null then normalizeHost cfg.hdhomerun.host else null;
  normalizePath = path: lib.removeSuffix "/" (toString path);
  sameOrUnder = parent: child:
    let
      p = normalizePath parent;
      c = normalizePath child;
    in
    c == p || lib.hasPrefix "${p}/" c;

  mkInputUrl = broadcast:
    let
      base =
        if hdhrBase != null then hdhrBase
        else if cfg.hdhomerun.deviceId != null then "http://${cfg.hdhomerun.deviceId}.local"
        else null;
    in
    "${base}/auto/v${broadcast.channel}";
  otelEnvironment =
    lib.optionalAttrs (cfg.observability.otelTracesEndpoint != null) {
      EVERY_CHANNEL_OTEL_TRACES_ENDPOINT = cfg.observability.otelTracesEndpoint;
      OTEL_SERVICE_NAME = cfg.observability.serviceName;
    };
  serviceEnvironment = {
    EVERY_CHANNEL_NODE_NAME = config.networking.hostName;
  } // cfg.environment // otelEnvironment;

  mkArchiveConvergenceProofService = proof:
    let
      unit = archiveConvergenceProofUnitName proof;
      sourceArgLines = lib.concatMapStrings (source: "cmd+=(--source ${lib.escapeShellArg source})\n") proof.sources;
      optionalArgLine = flag: value:
        lib.optionalString (value != null) "cmd+=(${flag} ${lib.escapeShellArg (toString value)})\n";
      runner = pkgs.writeShellApplication {
        name = unit;
        runtimeInputs = [
          cfg.package
        ];
        text = ''
          set -euo pipefail

          cmd=(
            ${lib.escapeShellArg "${cfg.package}/bin/ec-node"}
            archive-convergence-serve
            --broadcast ${lib.escapeShellArg proof.broadcast}
            --track ${lib.escapeShellArg proof.track}
            --listen ${lib.escapeShellArg proof.listen}
            --metrics-role ${lib.escapeShellArg proof.metricsRole}
          )
          ${sourceArgLines}
          ${optionalArgLine "--stream-id" proof.streamId}
          ${optionalArgLine "--rendition" proof.rendition}
          ${optionalArgLine "--start-sequence" proof.startSequence}
          ${optionalArgLine "--end-sequence" proof.endSequence}
          ${optionalArgLine "--metrics-node" proof.metricsNode}

          exec "''${cmd[@]}"
        '';
      };
    in
    {
      name = unit;
      value = {
        description = "every.channel archive convergence metrics (${proof.name})";
        wantedBy = [ "multi-user.target" ];
        after = [ "network-online.target" ];
        wants = [ "network-online.target" ];

        unitConfig = {
          StartLimitIntervalSec = 0;
        };

        serviceConfig = {
          Type = "simple";
          ExecStart = "${runner}/bin/${unit}";
          Restart = "always";
          RestartSec = 2;

          NoNewPrivileges = true;
          PrivateTmp = true;
          ProtectSystem = "strict";
          ProtectHome = true;
          ProtectKernelTunables = true;
          ProtectKernelModules = true;
          ProtectControlGroups = true;
          LockPersonality = true;
          MemoryDenyWriteExecute = true;
          RestrictSUIDSGID = true;
          RestrictRealtime = true;
          SystemCallArchitectures = "native";
        };

        environment = serviceEnvironment;
      };
    };
  mkArchiveConvergenceMeasureProofService = proof:
    let
      unit = archiveConvergenceMeasureProofUnitName proof;
      agentArgLines = lib.concatMapStrings (source: "cmd+=(--agent-manifest ${lib.escapeShellArg source})\n") proof.agentManifests;
      agentPrometheusSdArgLines = lib.concatMapStrings (source: "cmd+=(--agent-prometheus-sd ${lib.escapeShellArg source})\n") proof.agentPrometheusSdFiles;
      agentPrometheusSdLabelArgLines = lib.concatMapStrings (label: "cmd+=(--agent-prometheus-sd-label ${lib.escapeShellArg label})\n") proof.agentPrometheusSdLabels;
      manifestArgLines = lib.concatMapStrings (source: "cmd+=(--manifest ${lib.escapeShellArg source})\n") proof.manifests;
      optionalArgLine = flag: value:
        lib.optionalString (value != null) "cmd+=(${flag} ${lib.escapeShellArg (toString value)})\n";
      runner = pkgs.writeShellApplication {
        name = unit;
        runtimeInputs = [
          cfg.package
        ];
        text = ''
          set -euo pipefail

          cmd=(
            ${lib.escapeShellArg "${cfg.package}/bin/ec-node"}
            archive-convergence-measure-serve
            --broadcast ${lib.escapeShellArg proof.broadcast}
            --track ${lib.escapeShellArg proof.track}
            --listen ${lib.escapeShellArg proof.listen}
            --agent-manifest-role ${lib.escapeShellArg proof.agentManifestRole}
            --timeout-ms ${toString proof.timeoutMs}
            --max-manifest-bytes ${toString proof.maxManifestBytes}
            --max-samples ${toString proof.maxSamples}
            --min-elapsed-seconds ${toString proof.minElapsedSeconds}
            --metrics-role ${lib.escapeShellArg proof.metricsRole}
          )
          ${agentArgLines}
          ${agentPrometheusSdArgLines}
          ${agentPrometheusSdLabelArgLines}
          ${manifestArgLines}
          ${optionalArgLine "--stream-id" proof.streamId}
          ${optionalArgLine "--rendition" proof.rendition}
          ${optionalArgLine "--start-sequence" proof.startSequence}
          ${optionalArgLine "--end-sequence" proof.endSequence}
          ${optionalArgLine "--prometheus-url" proof.prometheusUrl}
          ${optionalArgLine "--metrics-node" proof.metricsNode}

          exec "''${cmd[@]}"
        '';
      };
    in
    {
      name = unit;
      value = {
        description = "every.channel remote archive convergence measurement metrics (${proof.name})";
        wantedBy = [ "multi-user.target" ];
        after = [ "network-online.target" ];
        wants = [ "network-online.target" ];

        unitConfig = {
          StartLimitIntervalSec = 0;
        };

        serviceConfig = {
          Type = "simple";
          ExecStart = "${runner}/bin/${unit}";
          Restart = "always";
          RestartSec = 2;

          NoNewPrivileges = true;
          PrivateTmp = true;
          ProtectSystem = "strict";
          ProtectHome = true;
          ProtectKernelTunables = true;
          ProtectKernelModules = true;
          ProtectControlGroups = true;
          LockPersonality = true;
          MemoryDenyWriteExecute = true;
          RestrictSUIDSGID = true;
          RestrictRealtime = true;
          SystemCallArchitectures = "native";
        };

        environment = serviceEnvironment;
      };
    };

in
{
  options.services.every-channel.ec-node = {
    enable = lib.mkEnableOption "every.channel ec-node WebTransport publisher services";

    package = lib.mkOption {
      type = lib.types.package;
      default = ecNodePkgDefault;
      defaultText = "pkgs.callPackage /path/to/every.channel/nix/pkgs/ec-node.nix {}";
      description = "The ec-node package to run.";
    };

    discoveryPackage = lib.mkOption {
      type = lib.types.package;
      default = ecCliPkgDefault;
      defaultText = "pkgs.callPackage /path/to/every.channel/nix/pkgs/ec-cli.nix {}";
      description = "Package used for HDHomeRun discovery when `hdhomerun.autoDiscover = true`.";
    };

    relayUrl = lib.mkOption {
      type = lib.types.str;
      default = "https://relay.every.channel/anon";
      description = "MoQ relay URL for ec-node wt-publish.";
    };

    relayAnnouncedWatchdogMs = lib.mkOption {
      type = lib.types.int;
      default = 90000;
      description = "Restart wt-publish when the relay /announced endpoint stops listing the broadcast for this many milliseconds. Set 0 to disable.";
    };

    relayAnnouncedWatchdogIntervalMs = lib.mkOption {
      type = lib.types.int;
      default = 10000;
      description = "Polling interval for the wt-publish relay announcement watchdog.";
    };

    transcode = lib.mkOption {
      type = lib.types.bool;
      default = true;
      description = "Whether ec-node should transcode to H.264/AAC before fragmenting.";
    };

    videoFilter = lib.mkOption {
      type = lib.types.str;
      default = "yadif=mode=send_frame:parity=auto:deint=interlaced,fps=30000/1001";
      description = "ffmpeg video filter passed to ec-node wt-publish when transcoding.";
    };

    gopFrames = lib.mkOption {
      type = lib.types.int;
      default = 30;
      description = "H.264 GOP/keyframe interval in frames for ec-node wt-publish.";
    };

    videoPreset = lib.mkOption {
      type = lib.types.str;
      default = "medium";
      description = "x264 preset for ec-node wt-publish. Slower presets trade publisher CPU for lower bitrate at the same CRF.";
    };

    videoCrf = lib.mkOption {
      type = lib.types.int;
      default = 23;
      description = "x264 CRF for ec-node wt-publish.";
    };

    publisherArchiveSegmentDurationMs = lib.mkOption {
      type = lib.types.int;
      default = 1001;
      description = "Publisher-origin proof segment duration passed to ec-node wt-publish.";
    };

    publisherStartBoundaryMs = lib.mkOption {
      type = lib.types.int;
      default = 1001;
      description = "Unix-epoch cadence boundary used before starting publisher ffmpeg.";
    };

    publisherArchive = {
      enable = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Enable publisher-origin source-window proof archive records for direct wt-publish units.";
      };

      outputDir = lib.mkOption {
        type = lib.types.str;
        default = "/srv/every-channel/archive-buffer";
        description = "Publisher-local CAS cache root passed to ec-node wt-publish.";
      };

      manifestDir = lib.mkOption {
        type = lib.types.str;
        default = "/srv/every-channel/archive-buffer/manifests";
        description = "Publisher-local manifest root passed to ec-node wt-publish.";
      };

      sourceNode = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Source node identity stamped into publisher-origin proof records.";
      };

      track = lib.mkOption {
        type = lib.types.str;
        default = "publisher.m4s";
        description = "Publisher-origin proof track name.";
      };
    };

    passthrough = lib.mkOption {
      type = lib.types.bool;
      default = false;
      description = "Compatibility flag for older moq-mux passthrough selection; moq-mux 0.4 preserves generated CMAF fragments.";
    };

    tlsDisableVerify = lib.mkOption {
      type = lib.types.bool;
      default = false;
      description = "Danger: disable TLS verification for the relay.";
    };

    extraArgs = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [ ];
      description = "Extra arguments appended to each ec-node wt-publish invocation.";
    };

    ntscRsCli = lib.mkOption {
      type = lib.types.nullOr lib.types.str;
      default = null;
      description = "Optional ntsc-rs-cli binary path used by broadcasts that set `ntscRsPreset`.";
    };

    environment = lib.mkOption {
      type = lib.types.attrsOf lib.types.str;
      default = {
        RUST_LOG = "info";
        # Cloudflare's relay is WebTransport-first; moq-native's WebSocket fallback can "win"
        # the race and then fail MoQ negotiation, causing a tight reconnect loop.
        MOQ_CLIENT_WEBSOCKET_ENABLED = "false";
      };
      description = "Environment variables for the publisher services.";
    };

    observability = {
      otelTracesEndpoint = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        example = "http://127.0.0.1:4320/v1/traces";
        description = "Optional OTLP/HTTP trace endpoint for ec-node Rust tracing spans.";
      };

      serviceName = lib.mkOption {
        type = lib.types.str;
        default = "ec-node";
        description = "OpenTelemetry service.name for ec-node processes.";
      };
    };

    hdhomerun = {
      host = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "HDHomeRun host/IP. When set, inputs are built as http://<host>/auto/v<channel>.";
      };

      deviceId = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "HDHomeRun device id (used as http://<deviceId>.local when host is unset).";
      };

      autoDiscover = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "If true and host is unset, attempt LAN discovery for the device id (best-effort).";
      };
    };

    control = {
      enable = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Enable iroh-gossip control announcements from each wt-publish service.";
      };

      ttlMs = lib.mkOption {
        type = lib.types.ints.positive;
        default = 15000;
        description = "Control announcement TTL passed to `ec-node wt-publish --control-ttl-ms`.";
      };

      intervalMs = lib.mkOption {
        type = lib.types.ints.positive;
        default = 5000;
        description = "Control announcement interval passed to `ec-node wt-publish --control-interval-ms`.";
      };

      discovery = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        example = "dht,mdns,dns";
        description = "Optional iroh discovery mode list for control announcements.";
      };

      irohSecret = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Optional iroh secret key (hex) for control announcement identity.";
      };

      gossipPeers = lib.mkOption {
        type = lib.types.listOf lib.types.str;
        default = [ ];
        description = "Optional iroh endpoint addresses to seed control gossip joins.";
      };

      bridgeWeb = {
        enable = lib.mkOption {
          type = lib.types.bool;
          default = false;
          description = "Run `ec-node control-bridge-web` to upsert announced streams into the web directory.";
        };

        directoryUrl = lib.mkOption {
          type = lib.types.str;
          default = "https://every.channel";
          description = "Directory base URL used by control-bridge-web for `/api/stream-upsert`.";
        };

        authTokenFile = lib.mkOption {
          type = lib.types.nullOr lib.types.str;
          default = null;
          description = "Optional file containing bearer token for `/api/stream-upsert`.";
        };

        timeoutMs = lib.mkOption {
          type = lib.types.ints.u32;
          default = 30000;
          description = "Bridge run timeout; service restarts and refreshes gossip peers after this window (0 = run forever).";
        };

        maxAgeMs = lib.mkOption {
          type = lib.types.ints.positive;
          default = 60000;
          description = "Maximum accepted staleness for control announcements consumed by the web bridge.";
        };

        streamPrefix = lib.mkOption {
          type = lib.types.nullOr lib.types.str;
          default = null;
          description = "Optional stream-id prefix filter for web upserts.";
        };

        discovery = lib.mkOption {
          type = lib.types.nullOr lib.types.str;
          default = null;
          example = "dht,mdns,dns";
          description = "Optional discovery mode override for the web bridge; falls back to `control.discovery` when unset.";
        };
      };
    };

    archive = {
      enable = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Run relay archival workers that subscribe and persist streams into CAS storage.";
      };

      directoryUrl = lib.mkOption {
        type = lib.types.str;
        default = "https://every.channel";
        description = "Directory base URL used to discover public streams (`/api/public-streams`).";
      };

      outputDir = lib.mkOption {
        type = lib.types.str;
        default = "/srv/every-channel/archive";
        description = "CAS object root passed to `ec-node wt-archive --output-dir`.";
      };

      manifestDir = lib.mkOption {
        type = lib.types.str;
        default = "/srv/every-channel/archive/manifests";
        description = "Manifest/index root passed to `ec-node wt-archive --manifest-dir`.";
      };

      pollIntervalMs = lib.mkOption {
        type = lib.types.ints.positive;
        default = 15000;
        description = "Discovery poll interval for public streams.";
      };

      relayUrlOverride = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Optional fixed relay URL override for archive workers (bypasses per-stream relay_url from directory entries).";
      };

      streamPrefix = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        description = "Optional broadcast-name prefix filter for archival workers.";
      };

      tracks = lib.mkOption {
        type = lib.types.listOf lib.types.str;
        default = [
          "catalog.json"
          "catalog"
          "init.mp4"
          "0.m4s"
          "1.m4s"
        ];
        description = "Tracks passed to each `wt-archive` worker.";
      };

      tlsDisableVerify = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Danger: disable TLS verification for relay archive subscribers.";
      };

      serve = {
        enable = lib.mkOption {
          type = lib.types.bool;
          default = false;
          description = "Run `ec-node wt-archive-serve` HTTP endpoints for archived replay/scrubbing.";
        };

        listen = lib.mkOption {
          type = lib.types.str;
          default = "0.0.0.0:7788";
          description = "Listen address passed to `ec-node wt-archive-serve --listen`.";
        };
      };

      convergence = {
        proofs = lib.mkOption {
          type = lib.types.listOf (lib.types.submodule {
            options = {
              name = lib.mkOption {
                type = lib.types.str;
                description = "Short unique name for this archive convergence proof service.";
              };

              sources = lib.mkOption {
                type = lib.types.listOf lib.types.str;
                default = [ ];
                example = [
                  "nuc-a=/srv/every-channel/archive-buffer/manifests"
                  "nuc-b=/srv/every-channel/archive-peer/manifests"
                ];
                description = "Archive source entries passed as repeated `ec-node archive-convergence-serve --source NAME=PATH` arguments.";
              };

              broadcast = lib.mkOption {
                type = lib.types.str;
                description = "Broadcast name passed to `archive-convergence-serve --broadcast`.";
              };

              track = lib.mkOption {
                type = lib.types.str;
                default = "publisher.m4s";
                description = "Archive track passed to `archive-convergence-serve --track`.";
              };

              streamId = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional stream id filter passed to `archive-convergence-serve --stream-id`.";
              };

              rendition = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional rendition filter passed to `archive-convergence-serve --rendition`.";
              };

              startSequence = lib.mkOption {
                type = lib.types.nullOr lib.types.int;
                default = null;
                description = "Optional start sequence passed to `archive-convergence-serve --start-sequence`.";
              };

              endSequence = lib.mkOption {
                type = lib.types.nullOr lib.types.int;
                default = null;
                description = "Optional end sequence passed to `archive-convergence-serve --end-sequence`.";
              };

              listen = lib.mkOption {
                type = lib.types.str;
                default = "127.0.0.1:7812";
                description = "Listen address for this proof service's `/health` and `/metrics` endpoints.";
              };

              metricsNode = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional node label override for emitted Prometheus metrics.";
              };

              metricsRole = lib.mkOption {
                type = lib.types.str;
                default = "duplicate-proof";
                description = "Role label for emitted Prometheus metrics.";
              };
            };
          });
          default = [ ];
          description = "Named Rust archive convergence proof services exposed as Prometheus scrape targets.";
        };

        remoteProofs = lib.mkOption {
          type = lib.types.listOf (lib.types.submodule {
            options = {
              name = lib.mkOption {
                type = lib.types.str;
                description = "Short unique name for this remote archive convergence measurement service.";
              };

              agentManifests = lib.mkOption {
                type = lib.types.listOf lib.types.str;
                default = [ ];
                example = [
                  "nuc-a=http://ec-publisher-a:7799"
                  "nuc-b=http://ec-publisher-b:7799"
                ];
                description = "Node-agent base URLs passed as repeated `ec-node archive-convergence-measure-serve --agent-manifest NAME=URL` arguments.";
              };

              agentPrometheusSdFiles = lib.mkOption {
                type = lib.types.listOf lib.types.str;
                default = [ ];
                example = [ "/var/lib/prometheus/every-channel-node-agents.json" ];
                description = "Prometheus file-SD JSON files passed as repeated `ec-node archive-convergence-measure-serve --agent-prometheus-sd PATH` arguments.";
              };

              agentPrometheusSdLabels = lib.mkOption {
                type = lib.types.listOf lib.types.str;
                default = [ ];
                example = [ "headscale_user=node" ];
                description = "Optional label filters passed as repeated `ec-node archive-convergence-measure-serve --agent-prometheus-sd-label KEY=VALUE` arguments.";
              };

              agentManifestRole = lib.mkOption {
                type = lib.types.str;
                default = "publisher-buffer";
                description = "Role query parameter for node-agent `/v1/archive-manifest` samples.";
              };

              manifests = lib.mkOption {
                type = lib.types.listOf lib.types.str;
                default = [ ];
                example = [
                  "nuc-a=https://publisher-a.example/manifests/la-kcop/publisher.m4s.jsonl"
                  "nuc-b=https://publisher-b.example/manifests/la-kcop/publisher.m4s.jsonl"
                ];
                description = "Direct manifest JSONL URLs passed as repeated `ec-node archive-convergence-measure-serve --manifest NAME=URL` arguments.";
              };

              broadcast = lib.mkOption {
                type = lib.types.str;
                description = "Broadcast name passed to `archive-convergence-measure-serve --broadcast`.";
              };

              track = lib.mkOption {
                type = lib.types.str;
                default = "publisher.m4s";
                description = "Archive track passed to `archive-convergence-measure-serve --track`.";
              };

              streamId = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional stream id filter passed to `archive-convergence-measure-serve --stream-id`.";
              };

              rendition = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional rendition filter passed to `archive-convergence-measure-serve --rendition`.";
              };

              startSequence = lib.mkOption {
                type = lib.types.nullOr lib.types.int;
                default = null;
                description = "Optional start sequence passed to `archive-convergence-measure-serve --start-sequence`.";
              };

              endSequence = lib.mkOption {
                type = lib.types.nullOr lib.types.int;
                default = null;
                description = "Optional end sequence passed to `archive-convergence-measure-serve --end-sequence`.";
              };

              prometheusUrl = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional Prometheus URL queried by the remote proof service for Grafana-facing duplicate/miss series.";
              };

              timeoutMs = lib.mkOption {
                type = lib.types.int;
                default = 10000;
                description = "HTTP timeout for each sampled manifest or Prometheus query.";
              };

              maxManifestBytes = lib.mkOption {
                type = lib.types.int;
                default = 4 * 1024 * 1024;
                description = "Maximum manifest bytes fetched per remote source sample.";
              };

              listen = lib.mkOption {
                type = lib.types.str;
                default = "127.0.0.1:7813";
                description = "Listen address for this remote proof service's `/health` and `/metrics` endpoints.";
              };

              maxSamples = lib.mkOption {
                type = lib.types.int;
                default = 16;
                description = "Maximum recent scrape samples retained for elapsed duplicate convergence proof.";
              };

              minElapsedSeconds = lib.mkOption {
                type = lib.types.int;
                default = 30;
                description = "Minimum elapsed sample window required before this remote proof reports ok.";
              };

              metricsNode = lib.mkOption {
                type = lib.types.nullOr lib.types.str;
                default = null;
                description = "Optional node label override for emitted Prometheus metrics.";
              };

              metricsRole = lib.mkOption {
                type = lib.types.str;
                default = "duplicate-proof";
                description = "Role label for emitted Prometheus metrics.";
              };
            };
          });
          default = [ ];
          description = "Named Rust remote archive convergence measurement services exposed as Prometheus scrape targets.";
        };
      };
    };

    nbc = {
      enable = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = "Enable Linux Chrome + virtual-display support for NBC browser-backed broadcasts.";
      };

      chromeBinary = lib.mkOption {
        type = lib.types.str;
        default = "/run/current-system/sw/bin/google-chrome-stable";
        description = "Chrome binary used by `ec-node nbc-bootstrap` and `ec-node nbc-wt-publish`.";
      };

      profileDir = lib.mkOption {
        type = lib.types.str;
        default = "/var/lib/every-channel/nbc-profile";
        description = "Persistent Chrome profile directory used for NBC / Adobe auth sessions.";
      };

      authScreenshotDir = lib.mkOption {
        type = lib.types.str;
        default = "/var/lib/every-channel/nbc-auth";
        description = "Directory for operator-facing screenshots when bootstrap hits an interactive auth page.";
      };

      display = lib.mkOption {
        type = lib.types.str;
        default = ":99";
        description = "DISPLAY used by the NBC virtual display session.";
      };

      screen = lib.mkOption {
        type = lib.types.str;
        default = "1920x1080x24";
        description = "Xvfb screen geometry for the NBC virtual display.";
      };

      noSandbox = lib.mkOption {
        type = lib.types.bool;
        default = true;
        description = "Pass `EVERY_CHANNEL_NBC_NO_SANDBOX=1` for Chrome worker sessions.";
      };

      mvpdProvider = lib.mkOption {
        type = lib.types.str;
        default = "Verizon Fios";
        description = "MVPD provider name used when the NBC worker must choose a TV provider.";
      };

      mvpdUsernameFile = lib.mkOption {
        type = lib.types.nullOr lib.types.path;
        default = null;
        description = "Optional root-managed file containing the MVPD username for unattended NBC login.";
      };

      mvpdPasswordFile = lib.mkOption {
        type = lib.types.nullOr lib.types.path;
        default = null;
        description = "Optional root-managed file containing the MVPD password for unattended NBC login.";
      };

      isolateWithUserNetns = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Launch NBC browser-backed workers inside a rootless user+network namespace backed by
          slirp4netns. This keeps the Chrome / ec-node process tree in its own network context
          while still using the host's active upstream route.
        '';
      };

      requireMullvad = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Refuse to start NBC browser-backed workers until `mullvad status` reports a connected
          tunnel. This assumes the host Mullvad daemon is already logged in and connected.
        '';
      };

      mullvadWaitSeconds = lib.mkOption {
        type = lib.types.ints.positive;
        default = 90;
        description = "Maximum time to wait for Mullvad connectivity before failing an NBC worker start.";
      };

      mullvadLocation = lib.mkOption {
        type = lib.types.nullOr lib.types.str;
        default = null;
        example = "USA";
        description = ''
          Optional case-insensitive substring that must appear in `mullvad status` before an NBC
          worker starts. Use this to pin workers to a country or city family without committing the
          operational login material itself.
        '';
      };

      vnc = {
        enable = lib.mkOption {
          type = lib.types.bool;
          default = true;
          description = "Expose the NBC virtual display over VNC so auth can be completed remotely when needed.";
        };

        listen = lib.mkOption {
          type = lib.types.str;
          default = "127.0.0.1";
          description = "x11vnc listen address for the NBC virtual display.";
        };

        port = lib.mkOption {
          type = lib.types.port;
          default = 5900;
          description = "x11vnc TCP port for the NBC virtual display.";
        };
      };
    };

    broadcasts = lib.mkOption {
      type = lib.types.listOf (lib.types.submodule {
        options = {
          name = lib.mkOption {
            type = lib.types.str;
            description = "Broadcast name published to the relay (used in watch links).";
          };
          channel = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = "HDHomeRun guide number (e.g. 4.1, 8.1). Required unless `input` is set.";
          };
          input = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = "Optional explicit ffmpeg input URL/file. When set, HDHomeRun settings are ignored for this broadcast.";
          };
          nbcUrl = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = "Optional NBC watch/live URL for a browser-backed relay publish worker.";
          };
          ntscRsPreset = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = "Optional ntsc-rs preset JSON rendered before wt-publish ffmpeg ingest.";
          };
          ntscRsOutput = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = "Optional ntsc-rs processed output path. Defaults to a private /tmp path for this service.";
          };
          ntscRsCli = lib.mkOption {
            type = lib.types.nullOr lib.types.str;
            default = null;
            description = "Optional per-broadcast ntsc-rs-cli binary path.";
          };
        };
      });
      default = [ ];
      description = "List of broadcasts (HDHomeRun, explicit input, or NBC browser-backed URL) to publish.";
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion =
          let
            needsHdhr = builtins.any (b: b.input == null && b.nbcUrl == null) cfg.broadcasts;
          in
          (!needsHdhr) || (cfg.hdhomerun.host != null) || (cfg.hdhomerun.deviceId != null);
        message = "Set services.every-channel.ec-node.hdhomerun.host or .deviceId (required when any broadcast omits `input`)";
      }
      {
        assertion = !(cfg.hdhomerun.autoDiscover && cfg.hdhomerun.host != null);
        message = "hdhomerun.autoDiscover only applies when hdhomerun.host is unset";
      }
      {
        assertion = cfg.publisherArchiveSegmentDurationMs > 0 && cfg.publisherStartBoundaryMs >= 0;
        message = "services.every-channel.ec-node publisher proof cadence values must be usable.";
      }
      {
        assertion =
          builtins.all
            (b:
              (lib.length (lib.filter (value: value != null) [ b.input b.channel b.nbcUrl ])) == 1)
            cfg.broadcasts;
        message = "Each broadcast must set exactly one of `input`, `channel`, or `nbcUrl`";
      }
      {
        assertion =
          let
            hasNbcBroadcast = builtins.any (b: b.nbcUrl != null) cfg.broadcasts;
          in
          (!hasNbcBroadcast) || cfg.nbc.enable;
        message = "Set services.every-channel.ec-node.nbc.enable = true before configuring `broadcasts.*.nbcUrl`";
      }
      {
        assertion = !cfg.archive.enable || !(sameOrUnder "/var/lib/every-channel" cfg.archive.outputDir);
        message = "services.every-channel.ec-node.archive.outputDir must not live under /var/lib/every-channel; put archive video CAS on /srv/every-channel, /tank/every-channel, or another data filesystem.";
      }
      {
        assertion =
          builtins.all
            (proof: lib.length proof.sources >= 2)
            cfg.archive.convergence.proofs;
        message = "Each services.every-channel.ec-node.archive.convergence.proofs entry must set at least two `sources` values.";
      }
      {
        assertion =
          let
            proofNames =
              (map (proof: proof.name) cfg.archive.convergence.proofs)
              ++ (map (proof: proof.name) cfg.archive.convergence.remoteProofs);
          in
          lib.length proofNames == lib.length (lib.unique proofNames);
        message = "services.every-channel.ec-node.archive.convergence proof names must be unique.";
      }
      {
        assertion =
          let
            proofListens =
              (map (proof: proof.listen) cfg.archive.convergence.proofs)
              ++ (map (proof: proof.listen) cfg.archive.convergence.remoteProofs);
          in
          lib.length proofListens == lib.length (lib.unique proofListens);
        message = "services.every-channel.ec-node.archive.convergence proof listen addresses must be unique.";
      }
      {
        assertion =
          builtins.all
            (proof:
              (proof.startSequence == null || proof.startSequence >= 0)
              && (proof.endSequence == null || proof.endSequence >= 0))
            cfg.archive.convergence.proofs;
        message = "services.every-channel.ec-node.archive.convergence.proofs sequence bounds must be non-negative.";
      }
      {
        assertion =
          builtins.all
            (proof:
              lib.length proof.agentManifests + lib.length proof.manifests >= 2
              || lib.length proof.agentPrometheusSdFiles > 0)
            cfg.archive.convergence.remoteProofs;
        message = "Each services.every-channel.ec-node.archive.convergence.remoteProofs entry must set at least two static sources or at least one `agentPrometheusSdFiles` value.";
      }
      {
        assertion =
          builtins.all
            (proof:
              (proof.startSequence == null || proof.startSequence >= 0)
              && (proof.endSequence == null || proof.endSequence >= 0))
            cfg.archive.convergence.remoteProofs;
        message = "services.every-channel.ec-node.archive.convergence.remoteProofs sequence bounds must be non-negative.";
      }
      {
        assertion =
          builtins.all
            (proof:
              proof.timeoutMs > 0
              && proof.maxManifestBytes > 0
              && proof.maxSamples >= 2
              && proof.minElapsedSeconds >= 0)
            cfg.archive.convergence.remoteProofs;
        message = "services.every-channel.ec-node.archive.convergence.remoteProofs timeout, manifest byte limit, sample count, and elapsed window must be usable.";
      }
    ];

    systemd.tmpfiles.rules =
      [
        "d /run/every-channel 1777 root root - -"
      ]
      ++ lib.optionals cfg.nbc.enable [
        "d /var/lib/every-channel 0750 every-channel every-channel - -"
        "d ${cfg.nbc.profileDir} 0750 every-channel every-channel - -"
        "d ${cfg.nbc.authScreenshotDir} 0750 every-channel every-channel - -"
      ]
      ++ lib.optionals cfg.archive.enable [
        "d ${cfg.archive.outputDir} 0750 root root - -"
        "d ${cfg.archive.manifestDir} 0750 root root - -"
      ]
      ++ lib.optionals cfg.publisherArchive.enable [
        "d ${cfg.publisherArchive.outputDir} 0750 root root - -"
        "d ${cfg.publisherArchive.outputDir}/objects 0750 root root - -"
        "d ${cfg.publisherArchive.outputDir}/objects/blake3 0750 root root - -"
        "d ${cfg.publisherArchive.manifestDir} 0750 root root - -"
      ];

    users.groups.every-channel = lib.mkIf cfg.nbc.enable { };
    users.users.every-channel = lib.mkIf cfg.nbc.enable {
      isSystemUser = true;
      group = "every-channel";
      home = "/var/lib/every-channel";
      createHome = true;
    };

    systemd.services =
      lib.listToAttrs (map
        (b:
          let
            unit = "every-channel-wt-publish-${sanitizeUnitName b.name}";
            isNbc = b.nbcUrl != null;
            ntscRsCli = if b.ntscRsCli != null then b.ntscRsCli else cfg.ntscRsCli;
            ntscRsOutput =
              if b.ntscRsOutput != null
              then b.ntscRsOutput
              else "/tmp/every-channel-ntsc-rs/${sanitizeUnitName b.name}.mp4";
            runner = pkgs.writeShellApplication {
              name = unit;
              runtimeInputs =
                [
                  pkgs.coreutils
                  pkgs.curl
                  pkgs.ffmpeg
                  pkgs.findutils
                  pkgs.gawk
                  pkgs.iproute2
                  nodeEntrypoint
                ]
                ++ lib.optionals (isNbc && cfg.nbc.requireMullvad) [ pkgs.mullvad-vpn ]
                ++ lib.optionals (isNbc && cfg.nbc.isolateWithUserNetns) [
                  pkgs.slirp4netns
                  pkgs.util-linux
                ]
                ++ lib.optionals cfg.hdhomerun.autoDiscover [ pkgs.jq cfg.discoveryPackage ];
              text =
                let
                  fixedHost = if cfg.hdhomerun.host != null then normalizeHost cfg.hdhomerun.host else "";
                  deviceId = cfg.hdhomerun.deviceId or "";
                  extraArgsLine =
                    if cfg.extraArgs == [ ] then
                      ""
                    else
                      "cmd+=(${lib.concatStringsSep " " (map lib.escapeShellArg cfg.extraArgs)})";
                  explicitInputStr = if b.input == null then "" else b.input;
                  channelStr = if b.channel == null then "" else b.channel;
                  nbcUrlStr = if b.nbcUrl == null then "" else b.nbcUrl;
                  controlEndpointOutPath = "/run/every-channel/control-peer-${sanitizeUnitName b.name}.json";
                  controlDiscoveryStr = if cfg.control.discovery == null then "" else cfg.control.discovery;
                  controlIrohSecretStr = if cfg.control.irohSecret == null then "" else cfg.control.irohSecret;
                  controlGossipPeerLines = lib.concatMapStrings (peer: "cmd+=(--gossip-peer ${lib.escapeShellArg peer})\n") cfg.control.gossipPeers;
                  nbcMullvadLocationStr = if cfg.nbc.mullvadLocation == null then "" else cfg.nbc.mullvadLocation;
                in
                ''
                  set -euo pipefail

                  wait_for_mullvad() {
                    local wait_seconds status expected
                    wait_seconds=${toString cfg.nbc.mullvadWaitSeconds}
                    expected=${lib.escapeShellArg nbcMullvadLocationStr}
                    for _ in $(seq 1 "$wait_seconds"); do
                      status="$(mullvad status 2>/dev/null || true)"
                      if [[ "$status" == Connected* ]]; then
                        if [[ -z "$expected" ]] || printf '%s\n' "$status" | grep -Fqi -- "$expected"; then
                          return 0
                        fi
                      fi
                      sleep 1
                    done

                    echo "ec-node: Mullvad was not connected${lib.optionalString (cfg.nbc.mullvadLocation != null) " to the expected location"} within ${toString cfg.nbc.mullvadWaitSeconds}s" >&2
                    mullvad status >&2 || true
                    return 1
                  }

                  run_in_user_netns() {
                    local tmpdir pid_file ready_fifo ns_pid slirp_pid status
                    tmpdir="$(mktemp -d /tmp/${unit}.usernet.XXXXXX)"
                    pid_file="$tmpdir/pid"
                    ready_fifo="$tmpdir/ready"
                    mkfifo "$ready_fifo"

                    # shellcheck disable=SC2016
                    unshare --user --map-root-user --net ${pkgs.bash}/bin/bash -lc '
                      set -euo pipefail
                      ip link set lo up
                      echo $$ > "$1"
                      read -r _ < "$2"
                      shift 2
                      exec "$@"
                    ' bash "$pid_file" "$ready_fifo" "''${cmd[@]}" &
                    ns_pid=$!

                    for _ in $(seq 1 50); do
                      [[ -s "$pid_file" ]] && break
                      sleep 0.1
                    done
                    if [[ ! -s "$pid_file" ]]; then
                      echo "ec-node: timed out waiting for NBC user-netns PID" >&2
                      kill "$ns_pid" 2>/dev/null || true
                      rm -rf "$tmpdir"
                      return 1
                    fi

                    slirp4netns --configure --mtu=1500 "$(cat "$pid_file")" tap0 >"$tmpdir/slirp.log" 2>&1 &
                    slirp_pid=$!
                    sleep 1
                    printf 'go\n' > "$ready_fifo"

                    set +e
                    wait "$ns_pid"
                    status=$?

                    kill "$slirp_pid" 2>/dev/null || true
                    wait "$slirp_pid" 2>/dev/null || true
                    rm -rf "$tmpdir"
                    return "$status"
                  }

                  nbc_url=${lib.escapeShellArg nbcUrlStr}
                  input=""
                  if [[ -z "$nbc_url" ]]; then
                    explicit_input=${lib.escapeShellArg explicitInputStr}
                    if [[ -n "$explicit_input" ]]; then
                      input="$explicit_input"
                    else
                      ch=${lib.escapeShellArg channelStr}
                      if [[ -z "$ch" ]]; then
                        echo "ec-node: broadcast missing input, channel, and nbcUrl" >&2
                        exit 2
                      fi

                      # Note: don't wrap lib.escapeShellArg in double-quotes, otherwise empty strings
                      # become a literal two-quote token and break discovery.
                      base=${lib.escapeShellArg fixedHost}
                      if [[ -z "$base" ]]; then
                        dev_id=${lib.escapeShellArg deviceId}
                        if [[ -z "$dev_id" ]]; then
                          echo "ec-node: missing hdhomerun.host and hdhomerun.deviceId" >&2
                          exit 2
                        fi

                        try_ip() {
                          local ip="$1"
                          local json id base_url
                          json="$(curl -fsS --connect-timeout 0.10 --max-time 0.20 "http://$ip/discover.json" 2>/dev/null || true)"
                          if [[ -z "$json" ]]; then
                            return 1
                          fi
                          id="$(printf '%s' "$json" | jq -r '.DeviceID // empty' 2>/dev/null || true)"
                          if [[ "$id" != "$dev_id" ]]; then
                            return 1
                          fi
                          base_url="$(printf '%s' "$json" | jq -r '.BaseURL // empty' 2>/dev/null || true)"
                          if [[ -z "$base_url" ]]; then
                            base_url="http://$ip"
                          fi
                          printf '%s\n' "$base_url"
                          return 0
                        }

                        if ${lib.boolToString cfg.hdhomerun.autoDiscover}; then
                          base="$(${cfg.discoveryPackage}/bin/ec-cli discover | jq -r --arg id "$dev_id" '.[] | select(.id == $id) | .base_url // empty' | head -n1 || true)"

                          if [[ -z "$base" ]]; then
                            while read -r ip; do
                              found="$(try_ip "$ip" || true)"
                              if [[ -n "$found" ]]; then
                                base="$found"
                                break
                              fi
                            done < <(ip neigh | awk '{print $1}' | sort -u)
                          fi

                          if [[ -z "$base" ]]; then
                            while read -r cidr; do
                              ip_addr="''${cidr%/*}"
                              prefix="''${cidr#*/}"
                              if [[ "$prefix" != "24" ]]; then
                                continue
                              fi
                              net="''${ip_addr%.*}"
                              for i in $(seq 1 254); do
                                found="$(try_ip "$net.$i" || true)"
                                if [[ -n "$found" ]]; then
                                  base="$found"
                                  break
                                fi
                              done
                              if [[ -n "$base" ]]; then
                                break
                              fi
                            done < <(ip -o -4 addr show scope global | awk '{print $4}')
                          fi

                          if [[ -z "$base" ]]; then
                            echo "ec-node: HDHomeRun deviceId not found: $dev_id" >&2
                            exit 2
                          fi
                        else
                          base="http://$dev_id.local"
                        fi
                      fi

                      base="''${base%/}"
                      if [[ "$base" != http://* && "$base" != https://* ]]; then
                        base="http://$base"
                      fi

                      hostport="''${base#http://}"
                      hostport="''${hostport#https://}"
                      hostport="''${hostport%%/*}"
                      host="''${hostport%%:*}"
                      input="http://$host:5004/auto/v$ch"
                    fi
                  fi

                  if [[ -n "$nbc_url" ]]; then
                    cmd=(
                      ${lib.escapeShellArg "${nodeEntrypoint}/bin/every-channel-node"}
                      nbc-wt-publish
                      --url ${lib.escapeShellArg cfg.relayUrl}
                      --name ${lib.escapeShellArg b.name}
                      --source-url "$nbc_url"
                      --gop-frames ${toString cfg.gopFrames}
                    )
                  else
                    cmd=(
                      ${lib.escapeShellArg "${nodeEntrypoint}/bin/every-channel-node"}
                      wt-publish
                      --url ${lib.escapeShellArg cfg.relayUrl}
                      --name ${lib.escapeShellArg b.name}
                      --input "$input"
                      --video-filter ${lib.escapeShellArg cfg.videoFilter}
                      --gop-frames ${toString cfg.gopFrames}
                      --video-preset ${lib.escapeShellArg cfg.videoPreset}
                      --video-crf ${toString cfg.videoCrf}
                      --relay-announced-watchdog-ms ${toString cfg.relayAnnouncedWatchdogMs}
                      --relay-announced-watchdog-interval-ms ${toString cfg.relayAnnouncedWatchdogIntervalMs}
                      --publisher-archive-segment-duration-ms ${toString cfg.publisherArchiveSegmentDurationMs}
                      --publisher-start-boundary-ms ${toString cfg.publisherStartBoundaryMs}
                    )
                    ${lib.optionalString cfg.publisherArchive.enable ''
                      cmd+=(--publisher-archive-output-dir ${lib.escapeShellArg cfg.publisherArchive.outputDir})
                      cmd+=(--publisher-archive-manifest-dir ${lib.escapeShellArg cfg.publisherArchive.manifestDir})
                      cmd+=(--publisher-archive-track ${lib.escapeShellArg cfg.publisherArchive.track})
                    ''}
                    ${lib.optionalString (cfg.publisherArchive.enable && cfg.publisherArchive.sourceNode != null) ''
                      cmd+=(--publisher-archive-source-node ${lib.escapeShellArg cfg.publisherArchive.sourceNode})
                    ''}
                    ${lib.optionalString (!cfg.transcode) "cmd+=(--transcode=false)"}
                    ${lib.optionalString (b.ntscRsPreset != null) ''
                      cmd+=(--ntsc-rs-preset ${lib.escapeShellArg b.ntscRsPreset})
                      cmd+=(--ntsc-rs-output ${lib.escapeShellArg ntscRsOutput})
                      ntsc_rs_cli=${lib.escapeShellArg (if ntscRsCli != null then ntscRsCli else "")}
                      if [[ -n "$ntsc_rs_cli" ]]; then
                        cmd+=(--ntsc-rs-cli "$ntsc_rs_cli")
                      fi
                    ''}
                  fi
                  ${lib.optionalString cfg.passthrough "cmd+=(--passthrough=true)"}
                  ${lib.optionalString cfg.tlsDisableVerify "cmd+=(--tls-disable-verify)"}
                  ${lib.optionalString cfg.control.enable ''
                    cmd+=(--control-announce)
                    cmd+=(--control-ttl-ms ${toString cfg.control.ttlMs})
                    cmd+=(--control-interval-ms ${toString cfg.control.intervalMs})
                    control_discovery=${lib.escapeShellArg controlDiscoveryStr}
                    if [[ -n "$control_discovery" ]]; then
                      cmd+=(--discovery "$control_discovery")
                    fi
                    control_iroh_secret=${lib.escapeShellArg controlIrohSecretStr}
                    if [[ -n "$control_iroh_secret" ]]; then
                      cmd+=(--iroh-secret "$control_iroh_secret")
                    fi
                    cmd+=(--control-endpoint-addr-out ${lib.escapeShellArg controlEndpointOutPath})
                    ${controlGossipPeerLines}
                  ''}
                  ${extraArgsLine}

                  # Keep the unit alive even if the relay is temporarily unreachable.
                  # This avoids `switch-to-configuration test` failing due to a unit that exits
                  # quickly during activation.
                  trap 'exit 0' INT TERM
                  is_nbc=${lib.boolToString isNbc}
                  failure_count=0
                  while true; do
                    ${lib.optionalString (isNbc && cfg.nbc.requireMullvad) ''
                      if ! wait_for_mullvad; then
                        sleep 30
                        continue
                      fi
                    ''}
                    started_at="$(date +%s)"
                    status=0
                    set +e
                    ${lib.optionalString (isNbc && cfg.nbc.isolateWithUserNetns) "run_in_user_netns"}
                    ${lib.optionalString (!isNbc || !cfg.nbc.isolateWithUserNetns) ''
                      "''${cmd[@]}"
                    ''}
                    status=$?
                    set -e
                    elapsed=$(( $(date +%s) - started_at ))
                    if [[ "$status" -eq 0 ]]; then
                      failure_count=0
                      sleep_seconds=2
                    else
                      failure_count=$(( failure_count + 1 ))
                      if [[ "$is_nbc" == "true" ]]; then
                        sleep_seconds=300
                        if [[ "$failure_count" -gt 1 ]]; then
                          for _ in $(seq 2 "$failure_count"); do
                            sleep_seconds=$(( sleep_seconds * 2 ))
                            if [[ "$sleep_seconds" -ge 1800 ]]; then
                              sleep_seconds=1800
                              break
                            fi
                          done
                        fi
                      else
                        sleep_seconds=$(( 2 * failure_count ))
                        if [[ "$sleep_seconds" -gt 60 ]]; then
                          sleep_seconds=60
                        fi
                      fi
                      echo "ec-node: command exited with status $status after ''${elapsed}s; retrying in ''${sleep_seconds}s" >&2
                    fi
                    sleep "$sleep_seconds"
                  done
                '';
            };
          in
          {
            name = unit;
            value = {
              description = "every.channel WebTransport publish (${b.name} -> ${cfg.relayUrl})";
              wantedBy = [ "multi-user.target" ];
              after =
                [ "network-online.target" ]
                ++ lib.optionals isNbc [ "every-channel-nbc-display.service" ]
                ++ lib.optionals (isNbc && cfg.nbc.requireMullvad) [ "mullvad-daemon.service" ];
              wants =
                [ "network-online.target" ]
                ++ lib.optionals isNbc [ "every-channel-nbc-display.service" ]
                ++ lib.optionals (isNbc && cfg.nbc.requireMullvad) [ "mullvad-daemon.service" ];

              # Keep the unit from entering "failed" due to rapid restarts (deploy-flake treats
              # failed units during `switch-to-configuration test` as a deployment failure).
              #
              # If the relay is temporarily unreachable, we want systemd to keep retrying without
              # tripping rate limits.
              unitConfig = {
                StartLimitIntervalSec = 0;
              };

              serviceConfig = {
                Type = "simple";
                ExecStart = "${runner}/bin/${unit}";
                Restart = "always";
                RestartSec = 2;

                DynamicUser = !isNbc && !cfg.publisherArchive.enable;
                User = lib.mkIf isNbc "every-channel";
                Group = lib.mkIf isNbc "every-channel";
                NoNewPrivileges = true;
                PrivateTmp = !isNbc;
                ProtectSystem = "strict";
                ProtectHome = !isNbc;
                ProtectKernelTunables = true;
                ProtectKernelModules = true;
                ProtectControlGroups = true;
                LockPersonality = true;
                MemoryDenyWriteExecute = !isNbc;
                RestrictSUIDSGID = true;
                RestrictRealtime = true;
                SystemCallArchitectures = "native";
                ReadWritePaths =
                  lib.optionals cfg.control.enable [ "/run/every-channel" ]
                  ++ lib.optionals cfg.publisherArchive.enable [
                    cfg.publisherArchive.outputDir
                    cfg.publisherArchive.manifestDir
                  ]
                  ++ lib.optionals isNbc [ "/tmp" ]
                  ++ lib.optionals isNbc [ cfg.nbc.profileDir cfg.nbc.authScreenshotDir ];
              };

              environment =
                serviceEnvironment
                // lib.optionalAttrs isNbc (
                  {
                    DISPLAY = cfg.nbc.display;
                    EVERY_CHANNEL_NBC_CHROME_PATH = cfg.nbc.chromeBinary;
                    EVERY_CHANNEL_NBC_MVPD_PROVIDER = cfg.nbc.mvpdProvider;
                    EVERY_CHANNEL_NBC_PROFILE_DIR = cfg.nbc.profileDir;
                    EVERY_CHANNEL_NBC_NO_SANDBOX = if cfg.nbc.noSandbox then "1" else "0";
                    HOME = "/var/lib/every-channel";
                  }
                  // lib.optionalAttrs (cfg.nbc.mvpdUsernameFile != null) {
                    EVERY_CHANNEL_NBC_MVPD_USERNAME_FILE = toString cfg.nbc.mvpdUsernameFile;
                  }
                  // lib.optionalAttrs (cfg.nbc.mvpdPasswordFile != null) {
                    EVERY_CHANNEL_NBC_MVPD_PASSWORD_FILE = toString cfg.nbc.mvpdPasswordFile;
                  }
                );
            };
          })
        cfg.broadcasts)
      // lib.optionalAttrs (cfg.control.enable && cfg.control.bridgeWeb.enable)
        (let
          bridgeUnit = "every-channel-control-bridge-web";
          bridgePeerFiles = map (b: "/run/every-channel/control-peer-${sanitizeUnitName b.name}.json") cfg.broadcasts;
          bridgeDiscoveryStr =
            if cfg.control.bridgeWeb.discovery != null then cfg.control.bridgeWeb.discovery
            else if cfg.control.discovery != null then cfg.control.discovery
            else "";
          bridgeStreamPrefixStr = if cfg.control.bridgeWeb.streamPrefix == null then "" else cfg.control.bridgeWeb.streamPrefix;
          bridgeAuthTokenFile = if cfg.control.bridgeWeb.authTokenFile == null then "" else cfg.control.bridgeWeb.authTokenFile;
          staticGossipPeerLines = lib.concatMapStrings (peer: ''
            cmd+=(--gossip-peer ${lib.escapeShellArg peer})
            have_peer=1
          '') cfg.control.gossipPeers;
          peerFileLoopLines = lib.concatMapStrings (peerFile: ''
            if [[ -s ${lib.escapeShellArg peerFile} ]]; then
              peer="$(tr -d '\r\n' < ${lib.escapeShellArg peerFile})"
              if [[ -n "$peer" ]]; then
                cmd+=(--gossip-peer "$peer")
                have_peer=1
              fi
            fi
          '') bridgePeerFiles;
          bridgeRunner = pkgs.writeShellApplication {
            name = bridgeUnit;
            runtimeInputs = [
              pkgs.coreutils
              nodeEntrypoint
            ];
            text = ''
              set -euo pipefail

              directory_url=${lib.escapeShellArg cfg.control.bridgeWeb.directoryUrl}
              bridge_discovery=${lib.escapeShellArg bridgeDiscoveryStr}
              stream_prefix=${lib.escapeShellArg bridgeStreamPrefixStr}
              auth_token_file=${lib.escapeShellArg bridgeAuthTokenFile}

              while true; do
                cmd=(
                  ${lib.escapeShellArg "${nodeEntrypoint}/bin/every-channel-node"}
                  control-bridge-web
                  --directory-url "$directory_url"
                  --timeout-ms ${toString cfg.control.bridgeWeb.timeoutMs}
                  --max-age-ms ${toString cfg.control.bridgeWeb.maxAgeMs}
                )

                if [[ -n "$bridge_discovery" ]]; then
                  cmd+=(--discovery "$bridge_discovery")
                fi
                if [[ -n "$stream_prefix" ]]; then
                  cmd+=(--stream-prefix "$stream_prefix")
                fi
                if [[ -n "$auth_token_file" && -r "$auth_token_file" ]]; then
                  token="$(tr -d '\r\n' < "$auth_token_file")"
                  if [[ -n "$token" ]]; then
                    cmd+=(--auth-token "$token")
                  fi
                fi

                have_peer=0
                ${peerFileLoopLines}
                ${staticGossipPeerLines}

                if [[ "$have_peer" -eq 0 ]]; then
                  sleep 2
                  continue
                fi

                "''${cmd[@]}" || true
                sleep 2
              done
            '';
          };
        in
        {
          "${bridgeUnit}" = {
            description = "every.channel control bridge to web directory";
            wantedBy = [ "multi-user.target" ];
            after =
              [ "network-online.target" ]
              ++ map (b: "every-channel-wt-publish-${sanitizeUnitName b.name}.service") cfg.broadcasts;
            wants =
              [ "network-online.target" ]
              ++ map (b: "every-channel-wt-publish-${sanitizeUnitName b.name}.service") cfg.broadcasts;

            unitConfig = {
              StartLimitIntervalSec = 0;
            };

            serviceConfig = {
              Type = "simple";
              ExecStart = "${bridgeRunner}/bin/${bridgeUnit}";
              Restart = "always";
              RestartSec = 2;

              DynamicUser = true;
              NoNewPrivileges = true;
              PrivateTmp = true;
              ProtectSystem = "strict";
              ProtectHome = true;
              ProtectKernelTunables = true;
              ProtectKernelModules = true;
              ProtectControlGroups = true;
              LockPersonality = true;
              MemoryDenyWriteExecute = true;
              RestrictSUIDSGID = true;
              RestrictRealtime = true;
              SystemCallArchitectures = "native";
            };

            environment = serviceEnvironment;
          };
        })
      // lib.optionalAttrs cfg.archive.enable
        (let
          archiveUnit = "every-channel-wt-archive-auto";
          archivePrefix = if cfg.archive.streamPrefix == null then "" else cfg.archive.streamPrefix;
          archiveTrackLines = lib.concatMapStrings (track: "                  cmd+=(--track ${lib.escapeShellArg track})\n") cfg.archive.tracks;
          archiveRunner = pkgs.writeShellApplication {
            name = archiveUnit;
            runtimeInputs = [
              pkgs.coreutils
              pkgs.curl
              pkgs.gawk
              pkgs.jq
              nodeEntrypoint
            ];
            text = ''
              set -euo pipefail

              directory_url=${lib.escapeShellArg (cfg.archive.directoryUrl + "/api/public-streams")}
              output_dir=${lib.escapeShellArg cfg.archive.outputDir}
              manifest_dir=${lib.escapeShellArg cfg.archive.manifestDir}
              relay_fallback=${lib.escapeShellArg cfg.relayUrl}
              relay_override=${lib.escapeShellArg (if cfg.archive.relayUrlOverride == null then "" else cfg.archive.relayUrlOverride)}
              stream_prefix=${lib.escapeShellArg archivePrefix}
              source_node="$(cat /proc/sys/kernel/hostname 2>/dev/null || echo unknown)"
              state_dir="/run/every-channel/archive"
              pids_dir="$state_dir/pids"
              logs_dir="$state_dir/logs"
              desired_file="$state_dir/desired"
              mkdir -p "$pids_dir" "$logs_dir"
              poll_secs="$(awk 'BEGIN { printf "%.3f", ${toString cfg.archive.pollIntervalMs} / 1000.0 }')"

              relay_is_owned() {
                local relay="$1"
                local host
                host="''${relay#https://}"
                host="''${host%%/*}"
                case "$host" in
                  relay.every.channel|lax.relay.every.channel|ord.relay.every.channel|nyc.relay.every.channel)
                    return 0
                    ;;
                esac
                return 1
              }

              cleanup_children() {
                for pid_file in "$pids_dir"/*.pid; do
                  [[ -e "$pid_file" ]] || continue
                  pid="$(cat "$pid_file" 2>/dev/null || true)"
                  if [[ -n "$pid" ]]; then
                    kill "$pid" 2>/dev/null || true
                  fi
                  rm -f "$pid_file"
                done
              }

              trap cleanup_children INT TERM EXIT

              while true; do
                : > "$desired_file"
                entries_json="$(curl -fsS "$directory_url" || true)"
                if [[ -n "$entries_json" ]]; then
                  while IFS= read -r entry; do
                    name="$(printf '%s\n' "$entry" | jq -r '.broadcast_name // empty')"
                    relay="$(printf '%s\n' "$entry" | jq -r '.relay_url // empty')"
                    if [[ -z "$name" ]]; then
                      continue
                    fi
                    if [[ -n "$stream_prefix" && "$name" != "$stream_prefix"* ]]; then
                      continue
                    fi
                    if [[ -n "$relay_override" ]]; then
                      relay="$relay_override"
                    elif [[ -z "$relay" ]]; then
                      relay="$relay_fallback"
                    fi
                    if ! relay_is_owned "$relay"; then
                      echo "ec-node: refusing to archive non-owned relay URL for $name: $relay" >&2
                      continue
                    fi

                    key="$(printf '%s' "$name" | tr -c 'A-Za-z0-9_.-' '_')"
                    printf '%s\n' "$key" >> "$desired_file"
                    pid_file="$pids_dir/$key.pid"
                    if [[ -s "$pid_file" ]]; then
                      pid="$(cat "$pid_file" 2>/dev/null || true)"
                      if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
                        continue
                      fi
                    fi

                    cmd=(
                      ${lib.escapeShellArg "${nodeEntrypoint}/bin/every-channel-node"}
                      wt-archive
                      --url "$relay"
                      --name "$name"
                      --output-dir "$output_dir"
                      --manifest-dir "$manifest_dir"
                      --source-node "$source_node"
                    )
                    ${lib.optionalString cfg.archive.tlsDisableVerify "cmd+=(--tls-disable-verify)"}
                    ${archiveTrackLines}

                    log_file="$logs_dir/$key.log"
                    (
                      exec "''${cmd[@]}"
                    ) >>"$log_file" 2>&1 &
                    echo "$!" > "$pid_file"
                  done < <(printf '%s\n' "$entries_json" | jq -rc '.entries[]?')
                fi

                for pid_file in "$pids_dir"/*.pid; do
                  [[ -e "$pid_file" ]] || continue
                  key="''${pid_file##*/}"
                  key="''${key%.pid}"
                  pid="$(cat "$pid_file" 2>/dev/null || true)"
                  if [[ -z "$pid" ]] || ! kill -0 "$pid" 2>/dev/null; then
                    rm -f "$pid_file"
                    continue
                  fi
                  if ! grep -Fxq "$key" "$desired_file"; then
                    echo "ec-node: stopping archive worker for no-longer-advertised stream $key" >&2
                    kill "$pid" 2>/dev/null || true
                    rm -f "$pid_file"
                  fi
                done

                sleep "$poll_secs"
              done
            '';
          };
        in
        {
          "${archiveUnit}" = {
            description = "every.channel relay archival auto-worker";
            wantedBy = [ "multi-user.target" ];
            after = [ "network-online.target" ];
            wants = [ "network-online.target" ];

            unitConfig = {
              StartLimitIntervalSec = 0;
            };

            serviceConfig = {
              Type = "simple";
              ExecStart = "${archiveRunner}/bin/${archiveUnit}";
              Restart = "always";
              RestartSec = 2;

              NoNewPrivileges = true;
              PrivateTmp = true;
              ProtectSystem = "strict";
              ProtectHome = true;
              ProtectKernelTunables = true;
              ProtectKernelModules = true;
              ProtectControlGroups = true;
              LockPersonality = true;
              MemoryDenyWriteExecute = true;
              RestrictSUIDSGID = true;
              RestrictRealtime = true;
              SystemCallArchitectures = "native";
              ReadWritePaths = [
                "/run/every-channel"
                cfg.archive.outputDir
                cfg.archive.manifestDir
              ];
            };

            environment = serviceEnvironment;
          };
        })
      // lib.optionalAttrs (cfg.archive.enable && cfg.archive.serve.enable)
        (let
          archiveServeUnit = "every-channel-wt-archive-serve";
          archiveServeRunner = pkgs.writeShellApplication {
            name = archiveServeUnit;
            runtimeInputs = [
              nodeEntrypoint
            ];
            text = ''
              set -euo pipefail
              exec ${lib.escapeShellArg "${nodeEntrypoint}/bin/every-channel-node"} \
                wt-archive-serve \
                --output-dir ${lib.escapeShellArg cfg.archive.outputDir} \
                --manifest-dir ${lib.escapeShellArg cfg.archive.manifestDir} \
                --listen ${lib.escapeShellArg cfg.archive.serve.listen}
            '';
          };
        in
        {
          "${archiveServeUnit}" = {
            description = "every.channel archived replay HTTP server";
            wantedBy = [ "multi-user.target" ];
            after = [ "network-online.target" ];
            wants = [ "network-online.target" ];

            unitConfig = {
              StartLimitIntervalSec = 0;
            };

            serviceConfig = {
              Type = "simple";
              ExecStart = "${archiveServeRunner}/bin/${archiveServeUnit}";
              Restart = "always";
              RestartSec = 2;

              NoNewPrivileges = true;
              PrivateTmp = true;
              ProtectSystem = "strict";
              ProtectHome = true;
              ProtectKernelTunables = true;
              ProtectKernelModules = true;
              ProtectControlGroups = true;
              LockPersonality = true;
              MemoryDenyWriteExecute = true;
              RestrictSUIDSGID = true;
              RestrictRealtime = true;
              SystemCallArchitectures = "native";
              ReadWritePaths = [
                cfg.archive.outputDir
                cfg.archive.manifestDir
              ];
            };

            environment = serviceEnvironment;
          };
        })
      // lib.listToAttrs (map mkArchiveConvergenceProofService cfg.archive.convergence.proofs)
      // lib.listToAttrs (map mkArchiveConvergenceMeasureProofService cfg.archive.convergence.remoteProofs)
      // lib.optionalAttrs cfg.nbc.enable
        (let
          displayUnit = "every-channel-nbc-display";
          displayNumber = lib.strings.removePrefix ":" cfg.nbc.display;
          displayRunner = pkgs.writeShellApplication {
            name = displayUnit;
            runtimeInputs = [
              pkgs.coreutils
              pkgs.xorg.xorgserver
            ];
            text = ''
              set -euo pipefail
              exec ${pkgs.xorg.xorgserver}/bin/Xvfb ${lib.escapeShellArg cfg.nbc.display} \
                -screen 0 ${lib.escapeShellArg cfg.nbc.screen} \
                -nolisten tcp \
                -ac \
                +extension RANDR
            '';
          };
          vncUnit = "every-channel-nbc-vnc";
          vncRunner = pkgs.writeShellApplication {
            name = vncUnit;
            runtimeInputs = [
              pkgs.x11vnc
            ];
            text = ''
              set -euo pipefail
              exec ${pkgs.x11vnc}/bin/x11vnc \
                -display ${lib.escapeShellArg cfg.nbc.display} \
                -forever \
                -shared \
                -nopw \
                -listen ${lib.escapeShellArg cfg.nbc.vnc.listen} \
                -rfbport ${toString cfg.nbc.vnc.port}
            '';
          };
        in
        ({
          "${displayUnit}" = {
            description = "every.channel NBC virtual display";
            wantedBy = [ "multi-user.target" ];
            after = [ "network-online.target" ];
            wants = [ "network-online.target" ];

            serviceConfig = {
              Type = "simple";
              ExecStart = "${displayRunner}/bin/${displayUnit}";
              Restart = "always";
              RestartSec = 2;
              User = "every-channel";
              Group = "every-channel";
              NoNewPrivileges = true;
              PrivateTmp = false;
              ProtectSystem = "strict";
              ProtectHome = false;
              ProtectKernelTunables = true;
              ProtectKernelModules = true;
              ProtectControlGroups = true;
              LockPersonality = true;
              MemoryDenyWriteExecute = false;
              RestrictSUIDSGID = true;
              RestrictRealtime = true;
              SystemCallArchitectures = "native";
              ReadWritePaths = [ "/tmp" "/var/lib/every-channel" ];
            };

            environment = serviceEnvironment // {
              HOME = "/var/lib/every-channel";
            };
          };
        }
        // lib.optionalAttrs cfg.nbc.vnc.enable {
          "${vncUnit}" = {
            description = "every.channel NBC virtual display VNC bridge";
            wantedBy = [ "multi-user.target" ];
            after = [ "network-online.target" "${displayUnit}.service" ];
            wants = [ "network-online.target" "${displayUnit}.service" ];

            serviceConfig = {
              Type = "simple";
              ExecStart = "${vncRunner}/bin/${vncUnit}";
              Restart = "always";
              RestartSec = 2;
              User = "every-channel";
              Group = "every-channel";
              NoNewPrivileges = true;
              PrivateTmp = false;
              ProtectSystem = "strict";
              ProtectHome = false;
              ProtectKernelTunables = true;
              ProtectKernelModules = true;
              ProtectControlGroups = true;
              LockPersonality = true;
              MemoryDenyWriteExecute = false;
              RestrictSUIDSGID = true;
              RestrictRealtime = true;
              SystemCallArchitectures = "native";
              ReadWritePaths = [ "/tmp" "/var/lib/every-channel" ];
            };

            environment = serviceEnvironment // {
              DISPLAY = cfg.nbc.display;
              HOME = "/var/lib/every-channel";
            };
          };
        }));
  };
}
