mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
Both Workspace jobs of run 33445831564 die at
roles/sys-svc-container/handlers/main.yml:9, on the first registry pull a nested
node's dockerd performs after a restart:
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/":
tls: failed to verify certificate: x509: certificate signed by unknown authority
The image name is a red herring. rpardini/docker-registry-proxy SSL-bumps, and the
certificate it presents for registry-1.docker.io and for ghcr.io carries the same
issuer, CN=DockerMirrorBox Intermediate IA -- measured against the live proxy through
the make helper, with three controls: direct fetch on public roots returns 401, the
same fetch through the proxy on public roots only returns "self-signed certificate in
certificate chain", and --cacert /dev/null errors, proving curl honours the flag.
That last control mattered: curl consults its compiled-in CApath as well, where
update-ca-certificates has hash-linked the proxy CA, so without --capath /nonexistent
the negative control passes silently.
So a mirror-name fix would have MOVED the error from docker.io to ghcr.io rather than
removing it. What is missing is trust, and the trust anchor came from
INFINITO_CACHE_REGISTRY_CA_HOST_PATH -- a variable act does not forward, and whose
`make dotenv` rewrites the shared .env back to default.env. It can therefore name a
directory the proxy that actually answers never signed with.
The bootstrap now reads the anchor off that proxy's own /ca bind. And it stops
limping: with the frontend up and no /ca bind exposed, the run aborts naming the
reason instead of continuing untrusting and surfacing three minutes later as a
cryptic x509.
Locally the patch is a measured no-op -- the inspected value is byte-identical to the
.env value, and the render with and without it is identical -- so it changes behaviour
only where the variable is wrong, which is act.
Not proven: that /mnt/cache/registry/ca/ca.crt on the hosted runner is the root the
live proxy presents. It follows from rpardini's design and from the sibling leg that
works, but both host directories are absent from every artifact and that runner is
gone. The line "==> registry-cache CA source: /mnt/cache/registry/ca" in the next
workspace run settles it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
125 lines
5.6 KiB
Bash
Executable File
125 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPOT for the cluster bring-up, one CI step: host-side compose build + up (node
|
|
# image layered on the distro's infinito image, so python + ansible + the CLI are
|
|
# baked -- no per-node .deb bootstrap), then every node concern (systemd wait,
|
|
# IPs, lab DNS, repo unpack) via compose/swarm/playbook.yml over the docker
|
|
# connection. Host-side pre-clean here is only the bind-mount dir + leftover
|
|
# containers; stale root-owned NFS writes are wiped in-node by the play (this
|
|
# often non-root act runner cannot delete them).
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
set -a
|
|
# shellcheck source=scripts/tests/deploy/swarm/utils/topology/base.sh
|
|
. "${SCRIPT_DIR}/../utils/topology/base.sh"
|
|
set +a
|
|
|
|
# shellcheck source=scripts/meta/env/load.sh
|
|
source "${SCRIPT_DIR}/../../../../../scripts/meta/env/load.sh"
|
|
|
|
: "${RUNNER_TEMP:?}" "${APP_ID:?}" "${INFINITO_DOMAIN:?}" "${INFINITO_CONTAINER:?}"
|
|
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
APT_TIMEOUT=10m
|
|
APT_OPTS=(-o Acquire::Retries=5 -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30)
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
timeout -k 30 "${APT_TIMEOUT}" apt-get "${APT_OPTS[@]}" update -qq || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
|
|
else
|
|
sudo -E timeout -k 30 "${APT_TIMEOUT}" apt-get "${APT_OPTS[@]}" update -qq || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
|
|
fi
|
|
fi
|
|
|
|
if docker exec "${INFINITO_CONTAINER}" docker ps --format '{{.Names}}' 2>/dev/null | grep -qx 'tor'; then
|
|
echo "==> quiescing CI compose tor (competing onion publisher)"
|
|
docker exec "${INFINITO_CONTAINER}" docker stop tor
|
|
fi
|
|
|
|
bash "${SCRIPT_DIR}/../utils/unmount/nfs_mounts.sh" "${NFS_SERVER}" >/dev/null 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
|
|
bash "${SCRIPT_DIR}/../utils/unmount/host_state.sh" "${INFINITO_DIR_VAR_LIB:?}"
|
|
for node in "${MGR}" "${WRK1}" "${WRK2}" "${NFS_SERVER}" "${BACKUP_NODE}"; do
|
|
docker rm -f "${node}" >/dev/null 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
|
|
done
|
|
docker volume rm "${SWARM_NAME}_nfs-export" >/dev/null 2>&1 || true # nocheck: shell-or-true -- grandfathered: worked in practice; TODO: sharpen to catch only the exact tolerated error
|
|
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../../.." && pwd)"
|
|
if [ -z "${INFINITO_IMAGE:-}" ]; then
|
|
INFINITO_IMAGE="$(bash "${REPO_ROOT}/scripts/meta/resolve/image/local.sh"):${INFINITO_IMAGE_TAG:?}"
|
|
export INFINITO_IMAGE
|
|
if ! docker image inspect "${INFINITO_IMAGE}" >/dev/null 2>&1; then
|
|
echo "==> building local infinito image ${INFINITO_IMAGE} for distro ${INFINITO_DISTRO}"
|
|
make -C "${REPO_ROOT}" build
|
|
fi
|
|
fi
|
|
|
|
COMPOSE_FILE="${SCRIPT_DIR}/../../../../../compose/swarm/compose.yml"
|
|
COMPOSE_ARGS=(-f "${COMPOSE_FILE}")
|
|
CACHE_FRONTEND="infinito-package-cache-frontend"
|
|
REGISTRY_CACHE="infinito-registry-cache"
|
|
CACHE_NET=""
|
|
if docker inspect "${CACHE_FRONTEND}" >/dev/null 2>&1; then
|
|
CACHE_NET="$(docker inspect "${CACHE_FRONTEND}" --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' | head -n1)"
|
|
echo "==> package-cache detected on ${CACHE_NET}; wiring swarm nodes to it"
|
|
registry_ca_src="$(docker inspect "${REGISTRY_CACHE}" \
|
|
--format '{{range .Mounts}}{{if eq .Destination "/ca"}}{{.Source}}{{end}}{{end}}' 2>/dev/null || true)"
|
|
if [ -z "${registry_ca_src}" ]; then
|
|
echo "FAILURE: ${CACHE_FRONTEND} is up but ${REGISTRY_CACHE} exposes no /ca bind; the nodes cannot learn its MITM CA" >&2
|
|
exit 1
|
|
fi
|
|
export INFINITO_CACHE_REGISTRY_CA_HOST_PATH="${registry_ca_src}"
|
|
echo "==> registry-cache CA source: ${INFINITO_CACHE_REGISTRY_CA_HOST_PATH}"
|
|
COMPOSE_ARGS+=(-f "${SCRIPT_DIR}/../../../../../compose/swarm/cache.override.yml")
|
|
fi
|
|
|
|
build_attempts=3
|
|
for attempt in $(seq 1 "${build_attempts}"); do
|
|
docker compose "${COMPOSE_ARGS[@]}" -p "${SWARM_NAME}" --profile drill build && break
|
|
if [ "${attempt}" -eq "${build_attempts}" ]; then
|
|
echo "FAILURE: node image build failed after ${build_attempts} attempts" >&2
|
|
exit 1
|
|
fi
|
|
sleep $((attempt * 5))
|
|
done
|
|
|
|
docker compose "${COMPOSE_ARGS[@]}" -p "${SWARM_NAME}" --profile drill up -d
|
|
|
|
if [ -n "${CACHE_NET}" ]; then
|
|
cache_subnet="$(docker network inspect "${CACHE_NET}" \
|
|
--format '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' 2>/dev/null |
|
|
grep -F . | head -n1 || true)"
|
|
case "${cache_subnet}" in
|
|
*.0/24) ;;
|
|
*)
|
|
echo "FAILURE: ${CACHE_NET} reports IPv4 subnet '${cache_subnet:-<none>}'; the node pinning below only holds for a x.y.z.0/24" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
cache_prefix="${cache_subnet%.0/24}"
|
|
|
|
node_octet=$((200 + INFINITO_INSTANCE * 5))
|
|
if [ "$((node_octet + 4))" -gt 254 ]; then
|
|
echo "FAILURE: INFINITO_INSTANCE=${INFINITO_INSTANCE} pushes the node band past ${cache_prefix}.254" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for node in "${MGR}" "${WRK1}" "${WRK2}" "${NFS_SERVER}" "${BACKUP_NODE}"; do
|
|
if ! connect_err="$(docker network connect --ip "${cache_prefix}.${node_octet}" "${CACHE_NET}" "${node}" 2>&1)"; then
|
|
case "${connect_err}" in
|
|
*"already exists in network"* | *"already attached to network"*)
|
|
echo "==> ${node} already on ${CACHE_NET}; keeping its current address" >&2
|
|
;;
|
|
*)
|
|
echo "FAILURE: cannot pin ${node} to ${cache_prefix}.${node_octet} on ${CACHE_NET}: ${connect_err}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
node_octet=$((node_octet + 1))
|
|
done
|
|
fi
|
|
|
|
ansible-playbook \
|
|
-i "${MGR},${WRK1},${WRK2},${NFS_SERVER},${BACKUP_NODE}," \
|
|
-c docker \
|
|
"${SCRIPT_DIR}/../../../../../compose/swarm/playbook.yml"
|