deploy/runner-podman-setup.sh

ff759b53942049b2043e132f5482b442a7265b98
gitbay/deploy/runner-podman-setup.sh history · blame · raw

108 lines · 4644 bytes · executable

  1#!/bin/sh
  2# Prepare a runner host for container-isolated builds (#144).
  3#
  4# Run this on the runner host as root BEFORE deploying a gitbay-runner
  5# that requires isolation. The runner refuses to start without a working
  6# podman rather than falling back to running builds unsandboxed, so the
  7# order matters: prepare the host, then `make deploy-runner`.
  8#
  9#   ssh -p 2222 root@bay1 'sh -s' < deploy/runner-podman-setup.sh
 10#
 11# Idempotent: safe to re-run.
 12set -eu
 13
 14RUNNER_USER="${RUNNER_USER:-ci-runner}"
 15
 16# The runner's home is wherever the account was created with; podman's
 17# store lives under it and the systemd drop-in names the same path.
 18
 19if ! id "$RUNNER_USER" >/dev/null 2>&1; then
 20    echo "no such user: $RUNNER_USER" >&2
 21    exit 1
 22fi
 23
 24echo "==> installing podman"
 25if ! command -v podman >/dev/null 2>&1; then
 26    apt-get update
 27    DEBIAN_FRONTEND=noninteractive apt-get install -y podman uidmap
 28fi
 29podman --version
 30
 31# nft loads the runner's host egress rule (#260,
 32# deploy/gitbay-runner-egress.nft), which `make deploy-runner` ships and
 33# the runner's unit requires. Without nft the runner does not start.
 34echo "==> installing nftables"
 35if ! command -v nft >/dev/null 2>&1; then
 36    apt-get update
 37    DEBIAN_FRONTEND=noninteractive apt-get install -y nftables
 38fi
 39nft --version
 40
 41# Rootless podman maps container uids into a range delegated to the user.
 42# Without these the runner's `podman run` fails with a mapping error.
 43echo "==> subuid/subgid for $RUNNER_USER"
 44for f in /etc/subuid /etc/subgid; do
 45    if ! grep -q "^$RUNNER_USER:" "$f" 2>/dev/null; then
 46        echo "$RUNNER_USER:200000:65536" >>"$f"
 47        echo "   added to $f"
 48    else
 49        echo "   already in $f"
 50    fi
 51done
 52
 53# User namespaces are what rootless podman is built on. Debian 13 enables
 54# them by default; check rather than assume, because a build silently
 55# running as the host user is exactly what this is meant to prevent.
 56echo "==> kernel support"
 57max_ns=$(cat /proc/sys/user/max_user_namespaces 2>/dev/null || echo 0)
 58if [ "$max_ns" -lt 1 ]; then
 59    echo "user namespaces are disabled (user.max_user_namespaces=$max_ns);" >&2
 60    echo "rootless podman cannot work until they are enabled" >&2
 61    exit 1
 62fi
 63echo "   max_user_namespaces=$max_ns"
 64
 65# podman's storage paths are pinned in storage.conf, both graphroot and
 66# runroot, under the runner's home. Left to podman, the run root is
 67# $XDG_RUNTIME_DIR or /tmp/storage-run-<uid>; the service runs with
 68# PrivateTmp, so that is a per-instance tmpfs, and podman's pause process
 69# (which the cgroupfs manager places outside the service cgroup) can
 70# outlive a restart holding a dead /tmp — after which every podman
 71# command, in any context, fails with "mkdir ...: no such file or
 72# directory". A run root under the home directory is valid in every
 73# namespace and needs neither lingering nor /tmp.
 74#
 75# podman records the run root at first use. Changing it later needs
 76# `podman system reset --force` as the runner user and a rebuild of the
 77# images; this script does not do that for you.
 78home=$(getent passwd "$RUNNER_USER" | cut -d: -f6)
 79conf="$home/.config/containers/storage.conf"
 80echo "==> storage config in $conf"
 81install -d -o "$RUNNER_USER" -g "$RUNNER_USER" -m 700 "$home/.config/containers"
 82printf '[storage]\ndriver = "overlay"\ngraphroot = "%s/.local/share/containers/storage"\nrunroot = "%s/.local/share/containers/run"\n' "$home" "$home" >"$conf"
 83chown "$RUNNER_USER:$RUNNER_USER" "$conf"
 84echo "   written"
 85
 86# podman sets net.ipv4.ping_group_range in every container by default,
 87# for unprivileged ping. The service runs with ProtectKernelTunables, so
 88# /proc/sys is read-only and crun fails to start the container with
 89# "open /proc/sys/net/ipv4/ping_group_range: Read-only file system". A
 90# build has no use for ping; drop the default rather than the hardening.
 91cconf="$home/.config/containers/containers.conf"
 92echo "==> container defaults in $cconf"
 93printf '[containers]\ndefault_sysctls = []\n' >"$cconf"
 94chown "$RUNNER_USER:$RUNNER_USER" "$cconf"
 95echo "   written"
 96
 97# Lingering keeps the user's systemd session alive when nobody is logged
 98# in, which podman's pause process relies on.
 99echo "==> lingering for $RUNNER_USER"
100loginctl enable-linger "$RUNNER_USER"
101
102echo "==> verifying rootless podman as $RUNNER_USER"
103# The verification fails rather than passing with || true: a host that
104# reports ready and is not is the outage this script exists to prevent.
105su - "$RUNNER_USER" -s /bin/sh -c "podman info --format 'rootless={{.Host.Security.Rootless}} runroot={{.Store.RunRoot}}'"
106
107echo
108echo "host is ready. Build the CI image (deploy/Containerfile.ci), then: make deploy-runner"