M3: bridge deployed + verified publicly reachable; webhook delivery blocked at Gitea (ALLOWED_HOST_LIST)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Bridge healthz 200 over public DNS; HMAC verified. Gitea sends no deliveries (suspect webhook host allowlist). Recorded in STATUS Blocked + operator options. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
107
modules/bridge.nix
Normal file
107
modules/bridge.nix
Normal file
@ -0,0 +1,107 @@
|
||||
# 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
|
||||
'';
|
||||
|
||||
image = pkgs.dockerTools.buildLayeredImage {
|
||||
name = "cc-ci-bridge";
|
||||
tag = "latest";
|
||||
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:latest
|
||||
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
|
||||
- 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";
|
||||
after = [ "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";
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -21,6 +21,12 @@
|
||||
secrets.drone_rpc_secret = { };
|
||||
secrets.drone_gitea_client_secret = { };
|
||||
|
||||
# M3 comment-bridge (A2). Read by modules/bridge.nix's reconcile oneshot, which copies them
|
||||
# into swarm secrets the bridge container mounts. webhook_hmac is also set on the Gitea webhook.
|
||||
secrets.bridge_webhook_hmac = { };
|
||||
secrets.bridge_drone_token = { };
|
||||
secrets.bridge_gitea_token = { };
|
||||
|
||||
# EnvironmentFile for the host exec runner: DRONE_RPC_SECRET rendered from the sops secret.
|
||||
templates."drone-runner.env".content = ''
|
||||
DRONE_RPC_SECRET=${config.sops.placeholder.drone_rpc_secret}
|
||||
|
||||
Reference in New Issue
Block a user