deploy/runner-podman-setup.sh
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"