# Comment-bridge (§4.1): the `!testme` webhook receiver. Packaged as a Nix-built OCI image
# (no Docker Hub pull) and run as a swarm service on `proxy`, routed by traefik at
# ci.commoninternet.net/hook. Deployed by an idempotent-reconcile oneshot (same pattern as
# proxy/drone). Secrets come from sops (/run/secrets) → swarm secrets the container mounts.
{ pkgs, ... }:
let
  # bridge.py placed at /app/bridge.py inside the image.
  bridgeApp = pkgs.runCommand "cc-ci-bridge-app" { } ''
    mkdir -p $out/app
    cp ${../../bridge/bridge.py} $out/app/bridge.py
  '';

  # Content-derived tag so `docker stack deploy` rolls the service whenever bridge.py changes
  # (a fixed `:latest` + unchanged stack spec does NOT roll — swarm sees no change).
  imageTag = builtins.substring 0 12 (builtins.hashString "sha256"
    (builtins.readFile ../../bridge/bridge.py));

  image = pkgs.dockerTools.buildLayeredImage {
    name = "cc-ci-bridge";
    tag = imageTag;
    contents = [ pkgs.python3 pkgs.cacert bridgeApp ];
    config = {
      Cmd = [ "${pkgs.python3}/bin/python3" "/app/bridge.py" ];
      Env = [ "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ];
      ExposedPorts = { "8080/tcp" = { }; };
    };
  };

  stack = pkgs.writeText "cc-ci-bridge-stack.yml" ''
    version: "3.8"
    services:
      app:
        image: cc-ci-bridge:${imageTag}
        environment:
          - GITEA_API=https://git.autonomic.zone/api/v1
          - DRONE_URL=https://drone.ci.commoninternet.net
          - CI_REPO=recipe-maintainers/cc-ci
          - BRIDGE_LISTEN=0.0.0.0:8080
          # Polling is PRIMARY (outbound, read-only, always on); the /hook webhook is an optional
          # admin-registered push optimization deduped against the poller (§4.1). Enrollment = add
          # the repo to POLL_REPOS (csv) + ensure tests/<recipe>/ exists.
          - POLL_INTERVAL=30
          - POLL_REPOS=recipe-maintainers/cc-ci,recipe-maintainers/custom-html,recipe-maintainers/custom-html-tiny,recipe-maintainers/keycloak,recipe-maintainers/cryptpad,recipe-maintainers/matrix-synapse,recipe-maintainers/lasuite-docs,recipe-maintainers/lasuite-meet,recipe-maintainers/n8n,recipe-maintainers/hedgedoc,recipe-maintainers/uptime-kuma,recipe-maintainers/bluesky-pds,recipe-maintainers/discourse,recipe-maintainers/ghost,recipe-maintainers/immich,recipe-maintainers/lasuite-drive,recipe-maintainers/mailu,recipe-maintainers/mattermost-lts,recipe-maintainers/mumble,recipe-maintainers/plausible,recipe-maintainers/drone,recipe-maintainers/gitea
          - HMAC_FILE=/run/secrets/webhook_hmac
          - DRONE_TOKEN_FILE=/run/secrets/drone_token
          - GITEA_TOKEN_FILE=/run/secrets/gitea_token
        secrets:
          - webhook_hmac
          - drone_token
          - gitea_token
        networks:
          - proxy
        deploy:
          replicas: 1
          restart_policy:
            condition: any
          labels:
            - "traefik.enable=true"
            - "traefik.http.services.ccci-bridge.loadbalancer.server.port=8080"
            - "traefik.http.routers.ccci-bridge.rule=Host(`ci.commoninternet.net`) && PathPrefix(`/hook`)"
            - "traefik.http.routers.ccci-bridge.entrypoints=web-secure"
            - "traefik.http.routers.ccci-bridge.tls=true"
    networks:
      proxy:
        external: true
    secrets:
      webhook_hmac:
        external: true
        name: cc_ci_bridge_webhook_hmac_v1
      drone_token:
        external: true
        name: cc_ci_bridge_drone_token_v1
      gitea_token:
        external: true
        name: cc_ci_bridge_gitea_token_v1
  '';

  reconcile = pkgs.writeShellApplication {
    name = "cc-ci-reconcile-bridge";
    runtimeInputs = with pkgs; [ docker coreutils ];
    text = ''
      for s in webhook_hmac drone_token gitea_token; do
        if [ ! -r "/run/secrets/bridge_$s" ]; then
          echo "FATAL: /run/secrets/bridge_$s missing (rebuild ordering?)" >&2
          exit 1
        fi
      done

      # Load the Nix-built image into the local docker (idempotent; layers cached).
      docker load -i ${image}

      # Materialise swarm secrets from sops (immutable; create once at v1).
      ensure_secret() {
        docker secret inspect "$2" >/dev/null 2>&1 || docker secret create "$2" "$1" >/dev/null
      }
      ensure_secret /run/secrets/bridge_webhook_hmac cc_ci_bridge_webhook_hmac_v1
      ensure_secret /run/secrets/bridge_drone_token  cc_ci_bridge_drone_token_v1
      ensure_secret /run/secrets/bridge_gitea_token  cc_ci_bridge_gitea_token_v1

      docker stack deploy --detach=true -c ${stack} ccci-bridge
    '';
  };
in
{
  systemd.services.deploy-bridge = {
    description = "Reconcile the cc-ci comment-bridge (!testme webhook) swarm service";
    # Serialized after deploy-drone (chain proxy→drone→bridge→dashboard→backupbot): on a FRESH host the
    # abra-driven reconcilers otherwise run concurrently against an uninitialised ~/.abra and race on
    # catalogue/recipe init, leaving units failed after a blank-VM rebuild. Ordering-only `after` fixes it.
    after = [ "deploy-drone.service" "deploy-proxy.service" "swarm-init.service" "docker.service" "network-online.target" ];
    requires = [ "swarm-init.service" "docker.service" ];
    wants = [ "network-online.target" ];
    wantedBy = [ "multi-user.target" ];
    serviceConfig = {
      Type = "oneshot";
      RemainAfterExit = true;
      ExecStart = "${reconcile}/bin/cc-ci-reconcile-bridge";
    };
  };
}
