From 1f8c5a3d03184c222f4e672e2de66932920d15da Mon Sep 17 00:00:00 2001 From: chengcheng Date: Fri, 17 Jul 2026 14:50:38 +0800 Subject: [PATCH] fix(ci): support digest-verified runner mirrors --- deploy/ci/easyai-gateway-ci-v2-runner.service | 3 +- scripts/provision-ci-runner.sh | 43 +++++++++++++++---- tests/ci/pipeline-test.sh | 13 +++++- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/deploy/ci/easyai-gateway-ci-v2-runner.service b/deploy/ci/easyai-gateway-ci-v2-runner.service index 29c8874..99556ee 100644 --- a/deploy/ci/easyai-gateway-ci-v2-runner.service +++ b/deploy/ci/easyai-gateway-ci-v2-runner.service @@ -9,9 +9,10 @@ Type=simple User=root Group=root Environment=RUNNER_SECURITY_OPTIONS= +Environment=RUNNER_IMAGE_REF= EnvironmentFile=-/etc/easyai-gateway-ci-v2/runner.env ExecStartPre=-/usr/bin/docker rm --force easyai-gateway-ci-v2-runner -ExecStart=/usr/bin/docker run --rm --pull=never --name easyai-gateway-ci-v2-runner --privileged --pids-limit 4096 $RUNNER_SECURITY_OPTIONS --volume easyai-gateway-ci-v2-data:/data --volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro --volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro --env CONFIG_FILE=/config.yaml --env DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns --env DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520 --env DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--pidns docker.io/gitea/runner:2.0.0-dind-rootless@sha256:5b7b625ff773d0ee761788c47582503ec1b241fa5b81edebad48a57e663f4f3a +ExecStart=/usr/bin/docker run --rm --pull=never --name easyai-gateway-ci-v2-runner --privileged --pids-limit 4096 $RUNNER_SECURITY_OPTIONS --volume easyai-gateway-ci-v2-data:/data --volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro --volume /etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro --volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro --env CONFIG_FILE=/config.yaml --env DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns --env DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520 --env DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--pidns $RUNNER_IMAGE_REF ExecStop=-/usr/bin/docker stop --time 360 easyai-gateway-ci-v2-runner Restart=always RestartSec=5s diff --git a/scripts/provision-ci-runner.sh b/scripts/provision-ci-runner.sh index 093e2fc..39ff207 100755 --- a/scripts/provision-ci-runner.sh +++ b/scripts/provision-ci-runner.sh @@ -25,6 +25,8 @@ COMPOSE_SHA256=f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959 PNPM_VERSION=10.18.1 RUNNER_IMAGE='docker.io/gitea/runner:2.0.0-dind-rootless@sha256:5b7b625ff773d0ee761788c47582503ec1b241fa5b81edebad48a57e663f4f3a' JOB_IMAGE='docker.io/library/node:24.16.0-bookworm@sha256:40ad9f3064e67d6860b4bc3fe1880b2953934fd6320ada990e45fe0efa6badd7' +RUNNER_IMAGE_SOURCE=${CI_RUNNER_IMAGE_SOURCE:-$RUNNER_IMAGE} +NESTED_DOCKER_REGISTRY_MIRROR=${CI_NESTED_DOCKER_REGISTRY_MIRROR:-} RUNNER_LABEL="easyai-gateway-ci-unprivileged-v2:docker://$JOB_IMAGE" fail() { @@ -47,6 +49,16 @@ case $RUNNER_IMAGE in *"/runner:${GITEA_RUNNER_VERSION}-dind-rootless@sha256:"*) ;; *) fail "RUNNER_IMAGE does not match GITEA_RUNNER_VERSION" ;; esac +runner_manifest_digest=${RUNNER_IMAGE##*@} +case $RUNNER_IMAGE_SOURCE in + *@"$runner_manifest_digest") ;; + *) fail "CI_RUNNER_IMAGE_SOURCE must use the pinned runner manifest digest" ;; +esac +if [ -n "$NESTED_DOCKER_REGISTRY_MIRROR" ] && \ + ! printf '%s\n' "$NESTED_DOCKER_REGISTRY_MIRROR" | \ + grep -Eq '^https://[A-Za-z0-9.-]+(:[0-9]+)?$'; then + fail "CI_NESTED_DOCKER_REGISTRY_MIRROR must be an HTTPS registry origin" +fi command -v docker >/dev/null 2>&1 || fail "Docker Engine is required" docker info >/dev/null 2>&1 || fail "Docker Engine is not reachable" grep -Fq "\"$RUNNER_LABEL\"" "$ROOT/deploy/ci/act-runner-v2-config.yaml" || \ @@ -176,10 +188,22 @@ chown -R root:root "$PREFIX" chmod -R go-w "$PREFIX" require_free_space "$MIN_FREE_GIB" "runner image pull" -docker pull "$RUNNER_IMAGE" +docker pull "$RUNNER_IMAGE_SOURCE" +runner_runtime_image=$(docker image inspect --format '{{.Id}}' "$RUNNER_IMAGE_SOURCE") +printf '%s\n' "$runner_runtime_image" | grep -Eq '^sha256:[0-9a-f]{64}$' || \ + fail "pinned runner image did not resolve to an immutable local image ID" require_free_space "$MIN_POST_INSTALL_FREE_GIB" "completed runner image pull" docker volume create "$RUNNER_VOLUME" >/dev/null +install -d -m 0755 /etc/easyai-gateway-ci-v2 +if [ -n "$NESTED_DOCKER_REGISTRY_MIRROR" ]; then + printf '{"registry-mirrors":["%s"]}\n' "$NESTED_DOCKER_REGISTRY_MIRROR" \ + > /etc/easyai-gateway-ci-v2/daemon.json +else + printf '{}\n' > /etc/easyai-gateway-ci-v2/daemon.json +fi +chmod 0644 /etc/easyai-gateway-ci-v2/daemon.json + # Docker's official rootless DinD baseline is --privileged. Gitea's example # additionally names an AppArmor profile, but Docker does not create that host # profile. Enable it only when this daemon proves it can start the pinned image @@ -187,7 +211,7 @@ docker volume create "$RUNNER_VOLUME" >/dev/null RUNNER_SECURITY_OPTIONS= if docker run --rm --pull=never --privileged \ --security-opt apparmor=rootlesskit \ - --entrypoint /bin/true "$RUNNER_IMAGE" >/dev/null 2>&1; then + --entrypoint /bin/true "$runner_runtime_image" >/dev/null 2>&1; then RUNNER_SECURITY_OPTIONS="--security-opt=apparmor=rootlesskit" elif [ -r /proc/sys/kernel/apparmor_restrict_unprivileged_userns ] && \ [ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" = "1" ]; then @@ -211,10 +235,11 @@ else fi docker run --detach --rm --pull=never \ --name "$PROBE_CONTAINER" --privileged --pids-limit 4096 "$@" \ + --volume /etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro \ --env DOCKERD_ROOTLESS_ROOTLESSKIT_NET=slirp4netns \ --env DOCKERD_ROOTLESS_ROOTLESSKIT_MTU=65520 \ --env DOCKERD_ROOTLESS_ROOTLESSKIT_FLAGS=--pidns \ - --entrypoint dockerd-entrypoint.sh "$RUNNER_IMAGE" >/dev/null + --entrypoint dockerd-entrypoint.sh "$runner_runtime_image" >/dev/null attempt=0 until docker exec "$PROBE_CONTAINER" docker info >/dev/null 2>&1; do attempt=$((attempt + 1)) @@ -232,10 +257,10 @@ docker exec "$PROBE_CONTAINER" docker info --format '{{range .SecurityOptions}}{ cleanup_probe trap - EXIT HUP INT TERM -install -d -m 0755 /etc/easyai-gateway-ci-v2 install -m 0644 "$ROOT/deploy/ci/act-runner-v2-config.yaml" /etc/easyai-gateway-ci-v2/config.yaml umask 077 -printf 'RUNNER_SECURITY_OPTIONS="%s"\n' "$RUNNER_SECURITY_OPTIONS" \ +printf 'RUNNER_SECURITY_OPTIONS="%s"\nRUNNER_IMAGE_REF="%s"\n' \ + "$RUNNER_SECURITY_OPTIONS" "$runner_runtime_image" \ > /etc/easyai-gateway-ci-v2/runner.env install -m 0644 "$ROOT/deploy/ci/easyai-gateway-ci-v2-runner.service" \ /etc/systemd/system/easyai-gateway-ci-v2-runner.service @@ -244,13 +269,13 @@ install -m 0644 "$ROOT/deploy/ci/easyai-gateway-ci-v2-runner.service" \ # root, then register and operate the runner as that image's rootless user. docker run --rm --pull=never --user 0:0 \ --volume "$RUNNER_VOLUME:/data" \ - --entrypoint /bin/sh "$RUNNER_IMAGE" \ + --entrypoint /bin/sh "$runner_runtime_image" \ -ec 'chown 1000:1000 /data && chmod 0700 /data' runner_is_registered() { docker run --rm --pull=never \ --volume "$RUNNER_VOLUME:/data" \ - --entrypoint /bin/sh "$RUNNER_IMAGE" \ + --entrypoint /bin/sh "$runner_runtime_image" \ -ec 'test -s /data/.runner' } @@ -265,12 +290,12 @@ if ! runner_is_registered; then --volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro \ --env HOME=/data \ --entrypoint /usr/local/bin/gitea-runner \ - "$RUNNER_IMAGE" --config /config.yaml register + "$runner_runtime_image" --config /config.yaml register fi unset RUNNER_REGISTRATION_TOKEN docker run --rm --pull=never \ --volume "$RUNNER_VOLUME:/data" \ - --entrypoint /bin/sh "$RUNNER_IMAGE" \ + --entrypoint /bin/sh "$runner_runtime_image" \ -ec 'test -s /data/.runner && chmod 0600 /data/.runner' RUNNER_VALIDATED=0 diff --git a/tests/ci/pipeline-test.sh b/tests/ci/pipeline-test.sh index 0f93558..bd433f6 100755 --- a/tests/ci/pipeline-test.sh +++ b/tests/ci/pipeline-test.sh @@ -123,8 +123,10 @@ grep -Fq 'EnvironmentFile=-/etc/easyai-gateway-ci-v2/runner.env' "$runner_servic grep -Fq '$RUNNER_SECURITY_OPTIONS' "$runner_service" grep -Fq -- "--volume easyai-gateway-ci-v2-data:/data" "$runner_service" grep -Fq -- "--volume /etc/easyai-gateway-ci-v2/config.yaml:/config.yaml:ro" "$runner_service" +grep -Fq -- "--volume /etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro" "$runner_service" grep -Fq -- "--volume /opt/easyai-gateway-ci:/opt/easyai-gateway-ci:ro" "$runner_service" -grep -Fq "$runner_image" "$runner_service" +grep -Fq 'Environment=RUNNER_IMAGE_REF=' "$runner_service" +grep -Fq '$RUNNER_IMAGE_REF' "$runner_service" if grep -Eq 'GITEA_RUNNER_REGISTRATION_TOKEN|/var/run/docker\.sock|/run/docker\.sock' "$runner_service"; then echo 'persistent runner service exposes a registration token or host Docker socket' >&2 exit 1 @@ -180,6 +182,11 @@ grep -q '^COMPOSE_VERSION=5.3.1$' "$provision" grep -q '^COMPOSE_SHA256=f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959$' "$provision" grep -Fq 'RUNNER_NAME=${RUNNER_NAME:-easyai-gateway-ci-unprivileged-v2}' "$provision" grep -Fq "RUNNER_IMAGE='$runner_image'" "$provision" +grep -Fq 'RUNNER_IMAGE_SOURCE=${CI_RUNNER_IMAGE_SOURCE:-$RUNNER_IMAGE}' "$provision" +grep -Fq 'runner_manifest_digest=${RUNNER_IMAGE##*@}' "$provision" +grep -Fq 'CI_RUNNER_IMAGE_SOURCE must use the pinned runner manifest digest' "$provision" +grep -Fq 'runner_runtime_image=$(docker image inspect' "$provision" +grep -Fq 'RUNNER_IMAGE_REF="%s"' "$provision" grep -Fq "JOB_IMAGE='$job_image'" "$provision" grep -Fq 'RUNNER_LABEL="easyai-gateway-ci-unprivileged-v2:docker://$JOB_IMAGE"' "$provision" grep -Fq 'docker volume create "$RUNNER_VOLUME"' "$provision" @@ -187,7 +194,9 @@ grep -Fq -- '--security-opt apparmor=rootlesskit' "$provision" grep -Fq 'RUNNER_SECURITY_OPTIONS="--security-opt=apparmor=rootlesskit"' "$provision" grep -Fq 'RUNNER_SECURITY_OPTIONS=' "$provision" grep -Fq '/proc/sys/kernel/apparmor_restrict_unprivileged_userns' "$provision" -grep -Fq -- '--entrypoint dockerd-entrypoint.sh "$RUNNER_IMAGE"' "$provision" +grep -Fq -- '--entrypoint dockerd-entrypoint.sh "$runner_runtime_image"' "$provision" +grep -Fq '/etc/easyai-gateway-ci-v2/daemon.json:/home/rootless/.config/docker/daemon.json:ro' "$provision" +grep -Fq 'CI_NESTED_DOCKER_REGISTRY_MIRROR must be an HTTPS registry origin' "$provision" grep -Fq 'PROBE_CONTAINER="easyai-gateway-ci-v2-probe-$$"' "$provision" grep -Fq -- '--entrypoint /usr/local/bin/gitea-runner' "$provision" grep -Fq 'shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz' "$provision"