mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
01_bootstrap.sh pins the five swarm nodes into the shared cache network at 200 + INFINITO_INSTANCE * 5. That band fits a /24 only for instances 0 to 10; from instance 11 on the script aborted with "pushes the node band past ...254", so no worktree past slot 10 could start a swarm cluster while the package cache runs. Five nodes per slot cannot fit 255 slots in one /24 with any fixed formula. Instances whose band fits keep their pinned addresses, so the primary checkout and CI behave as before. Above that, the nodes join the cache network without --ip and Docker assigns them from its pool (the upper half of the subnet), the same way every worktree stack container joins it. Nothing in the repository reads the nodes' cache-network addresses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.8 KiB
Bash
Executable File
130 lines
5.8 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 --preserve-env="$(compgen -e | paste -sd, -)" 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))
|
|
pin_nodes=true
|
|
if [ "$((node_octet + 4))" -gt 254 ]; then
|
|
echo "==> INFINITO_INSTANCE=${INFINITO_INSTANCE} leaves no pinned node band in ${cache_prefix}.0/24; Docker assigns the node addresses from ${CACHE_NET}'s pool" >&2
|
|
pin_nodes=false
|
|
fi
|
|
|
|
for node in "${MGR}" "${WRK1}" "${WRK2}" "${NFS_SERVER}" "${BACKUP_NODE}"; do
|
|
connect_args=()
|
|
if [ "${pin_nodes}" = true ]; then
|
|
connect_args=(--ip "${cache_prefix}.${node_octet}")
|
|
fi
|
|
if ! connect_err="$(docker network connect "${connect_args[@]}" "${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 attach ${node} to ${CACHE_NET} (${connect_args[*]:-pool address}): ${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"
|