| 1 | #!/bin/bash |
| 2 | # Hosted Diffinery runners: starts the Docker daemon the first time anything needs |
| 3 | # it (a step running `docker`, job containers, services, Docker actions), then runs |
| 4 | # the real CLI. The daemon runs in its own session so step cleanup leaves it alone. |
| 5 | # |
| 6 | # The daemon reaches the Diffinery container registry through the runner's registry |
| 7 | # relay (agent.mjs --registry-relay), which splits image layers into chunks under |
| 8 | # Cloudflare's 100 MB request limit. Builders that `docker buildx create` starts |
| 9 | # (docker/setup-buildx-action) are pointed at the relay too. |
| 10 | real=/usr/bin/docker |
| 11 | ready=/run/diffinery-dockerd.ready |
| 12 | state=/run/diffinery |
| 13 | relay_port=3129 |
| 14 | sys_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin |
| 15 | |
| 16 | # The Diffinery host this runner reports to, when it is reached over HTTPS. |
| 17 | registry_host() { |
| 18 | local url=${DIFFINERY_API_URL:-} |
| 19 | [[ "$url" == https://* ]] || return 1 |
| 20 | url=${url#https://} |
| 21 | echo "${url%%/*}" |
| 22 | } |
| 23 | |
| 24 | start_relay() { |
| 25 | local host=$1 |
| 26 | mkdir -p "$state" "/etc/docker/certs.d/$host" || return 1 |
| 27 | # A certificate for the Diffinery host that only this daemon (and its builders) trust. |
| 28 | openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 30 -subj "/CN=$host" \ |
| 29 | -addext "subjectAltName=DNS:$host" -keyout "$state/relay.key" -out "$state/relay.crt" >/dev/null 2>&1 || return 1 |
| 30 | cp "$state/relay.crt" "/etc/docker/certs.d/$host/ca.crt" |
| 31 | printf '[registry."%s"]\n ca = ["%s"]\n' "$host" "$state/relay.crt" >"$state/buildkitd.toml" |
| 32 | env -i PATH="$sys_path" DIFFINERY_RELAY_HOST="$host" DIFFINERY_RELAY_UPSTREAM="https://$host" DIFFINERY_RELAY_PORT="$relay_port" \ |
| 33 | DIFFINERY_RELAY_CERT="$state/relay.crt" DIFFINERY_RELAY_KEY="$state/relay.key" \ |
| 34 | setsid -f node /opt/diffinery/agent.mjs --registry-relay >/var/log/diffinery-relay.log 2>&1 </dev/null |
| 35 | for _ in $(seq 1 80); do |
| 36 | if (exec 3<>"/dev/tcp/127.0.0.1/$relay_port") 2>/dev/null; then |
| 37 | echo "$host" >"$state/relay.host" |
| 38 | return 0 |
| 39 | fi |
| 40 | sleep 0.25 |
| 41 | done |
| 42 | return 1 |
| 43 | } |
| 44 | |
| 45 | if [ ! -e "$ready" ] && [ -z "${DOCKER_HOST:-}" ] && [ "$(id -u)" = 0 ]; then |
| 46 | exec 9>/run/diffinery-dockerd.lock |
| 47 | flock 9 |
| 48 | if [ ! -e "$ready" ]; then |
| 49 | if ! "$real" version >/dev/null 2>&1; then |
| 50 | echo "Starting the Docker daemon" >&2 |
| 51 | proxy=() |
| 52 | if host=$(registry_host) && start_relay "$host"; then |
| 53 | proxy=(HTTPS_PROXY="http://127.0.0.1:$relay_port") |
| 54 | elif [ -n "${host:-}" ]; then |
| 55 | echo "The registry relay did not start, so image layers over 100 MB can't be pushed to $host:" >&2 |
| 56 | tail -n 5 /var/log/diffinery-relay.log >&2 2>/dev/null |
| 57 | fi |
| 58 | env -i PATH="$sys_path" "${proxy[@]}" setsid -f dockerd >/var/log/dockerd.log 2>&1 </dev/null |
| 59 | for _ in $(seq 1 240); do "$real" version >/dev/null 2>&1 && break; sleep 0.25; done |
| 60 | "$real" version >/dev/null 2>&1 || { echo "The Docker daemon did not start:" >&2; tail -n 20 /var/log/dockerd.log >&2; exit 1; } |
| 61 | fi |
| 62 | touch "$ready" |
| 63 | fi |
| 64 | exec 9>&- |
| 65 | fi |
| 66 | |
| 67 | # docker buildx create (docker-container driver): run BuildKit on the host network with |
| 68 | # the relay as its HTTPS proxy, trusting the relay for the Diffinery host. |
| 69 | if [ "${1:-}" = buildx ] && [ "${2:-}" = create ] && [ -s "$state/relay.host" ]; then |
| 70 | host=$(cat "$state/relay.host") |
| 71 | args=("$@") |
| 72 | driver=docker-container |
| 73 | cfg=-1 |
| 74 | for ((i = 2; i < ${#args[@]}; i++)); do |
| 75 | case "${args[i]}" in |
| 76 | --driver) driver=${args[i + 1]:-} ;; |
| 77 | --driver=*) driver=${args[i]#--driver=} ;; |
| 78 | --buildkitd-config | --config) cfg=$((i + 1)) ;; |
| 79 | --driver-opt) [[ "${args[i + 1]:-}" == network=* ]] && driver=custom-network ;; |
| 80 | --driver-opt=network=*) driver=custom-network ;; |
| 81 | esac |
| 82 | done |
| 83 | if [ "$driver" = docker-container ]; then |
| 84 | if [ "$cfg" -ge 0 ] && [ -f "${args[cfg]}" ]; then |
| 85 | if ! grep -qF "registry.\"$host\"" "${args[cfg]}"; then |
| 86 | merged=$(mktemp "$state/buildkitd-XXXXXX.toml") |
| 87 | { cat "${args[cfg]}"; echo; cat "$state/buildkitd.toml"; } >"$merged" |
| 88 | args[cfg]=$merged |
| 89 | fi |
| 90 | elif [ "$cfg" -lt 0 ]; then |
| 91 | args+=(--buildkitd-config "$state/buildkitd.toml") |
| 92 | fi |
| 93 | args+=(--driver-opt network=host --driver-opt "env.HTTPS_PROXY=http://127.0.0.1:$relay_port" --driver-opt "env.https_proxy=http://127.0.0.1:$relay_port") |
| 94 | exec "$real" "${args[@]}" |
| 95 | fi |
| 96 | fi |
| 97 | |
| 98 | exec "$real" "$@" |