vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
|
d0d4239f
Keenan Nemetz
3d ago
|
1 | #cloud-config |
| 2 | # A Vole CI runner on a fresh Ubuntu or Debian machine. | |
| 3 | # | |
| 4 | # A runner leases jobs over SSH, executes each one in a container, and reports the | |
| 5 | # result. It is a separate machine from the instance on purpose: it runs | |
| 6 | # repository code, and a runner can read every secret its jobs declare, so it | |
| 7 | # should not share a disk with the account database and the SSH host key. | |
| 8 | # | |
|
51624dc6
Keenan Nemetz
3d ago
|
9 | # Each placeholder below is a value the caller supplies, and render.sh fills them |
| 10 | # in: a placeholder is a dollar sign followed by a name in braces, so shell | |
| 11 | # variables in the script at the bottom are written without braces and are left | |
| 12 | # alone. scripts/check-deploy.py renders this file the same way to check it. | |
|
d0d4239f
Keenan Nemetz
3d ago
|
13 | |
| 14 | hostname: ${runner_hostname} | |
| 15 | fqdn: ${runner_hostname} | |
| 16 | manage_etc_hosts: true | |
| 17 | ||
| 18 | package_update: true | |
| 19 | packages: | |
| 20 | - ca-certificates | |
| 21 | - curl | |
| 22 | - git | |
| 23 | - openssh-client | |
| 24 | # Rootless podman and what it needs to map the runner's user into the container | |
| 25 | # and to give a container a network when a job asks for one. | |
| 26 | - podman | |
| 27 | - slirp4netns | |
| 28 | - fuse-overlayfs | |
| 29 | - uidmap | |
| 30 | ||
| 31 | write_files: | |
| 32 | # Staged, not installed directly: the package ships /etc/vole/runner.env as a | |
| 33 | # conffile, so it has to arrive after the package does. | |
| 34 | - path: /opt/vole-runner/runner.env | |
| 35 | permissions: "0644" | |
| 36 | content: | | |
| 37 | # Read by vole-runner.service. One line, split at spaces. | |
|
4e3759da
Keenan Nemetz
51m ago
|
38 | VOLE_RUNNER_ARGS=--remote ssh://git@${forge_host}:2222 --container-runtime ${runner_runtime} --container-network ${runner_network} --dir ${runner_dir} ${runner_jobs} --known-hosts /etc/vole/runner_known_hosts ${runner_labels} --container-memory ${runner_memory} --container-pids ${runner_pids} --container-tmpfs ${runner_tmpfs} --disk-reserve ${runner_disk_reserve} |
|
d0d4239f
Keenan Nemetz
3d ago
|
39 | |
| 40 | - path: /usr/local/sbin/vole-runner-first-boot | |
| 41 | permissions: "0755" | |
| 42 | content: | | |
| 43 | #!/bin/sh | |
| 44 | # One-time setup for a Vole CI runner. cloud-init runs this once, on first | |
| 45 | # boot, and it is safe to run again: the package install is skipped when | |
| 46 | # vole is already there, and the account and key are only created if they | |
| 47 | # are missing. | |
| 48 | set -eu | |
| 49 | ||
| 50 | FORGE_HOST='${forge_host}' | |
| 51 | PACKAGE_URL='${vole_package_url}' | |
| 52 | RUNNER_DIR='${runner_dir}' | |
| 53 | RUNNER_USER=ci-runner | |
| 54 | LOG=/var/log/vole-runner-first-boot.log | |
| 55 | ||
| 56 | exec >>"$LOG" 2>&1 | |
| 57 | echo "== vole runner first boot $(date -u +%Y-%m-%dT%H:%M:%SZ) ==" | |
| 58 | ||
| 59 | # The package installs the binary, the runner unit, and /etc/vole/runner.env. | |
| 60 | # It also prepares a server instance that this machine never starts. | |
| 61 | if ! command -v vole >/dev/null 2>&1; then | |
| 62 | echo "downloading $PACKAGE_URL" | |
| 63 | curl -fsSL --retry 5 --retry-delay 2 -o /tmp/vole.deb "$PACKAGE_URL" | |
| 64 | DEBIAN_FRONTEND=noninteractive apt-get install -y /tmp/vole.deb | |
| 65 | rm -f /tmp/vole.deb | |
| 66 | fi | |
| 67 | ||
| 68 | # A dedicated account with a home directory and a range of subordinate ids, | |
| 69 | # which is what lets the container runtime be rootless. It is not the vole | |
| 70 | # service account: a job runs as this user, and the forge's own account does | |
| 71 | # not exist on this machine. | |
| 72 | if ! getent passwd "$RUNNER_USER" >/dev/null 2>&1; then | |
| 73 | useradd --create-home --home-dir "$RUNNER_DIR" --shell /usr/sbin/nologin \ | |
| 74 | --comment "Vole CI runner" "$RUNNER_USER" | |
| 75 | fi | |
| 76 | if ! grep -q "^$RUNNER_USER:" /etc/subuid; then | |
| 77 | usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$RUNNER_USER" | |
| 78 | fi | |
| 79 | ||
| 80 | # The runner authenticates with this key. Its public half is what an | |
| 81 | # instance admin registers as a bot account; nothing here talks to the | |
| 82 | # instance as an admin. | |
| 83 | if [ ! -f "$RUNNER_DIR/.ssh/id_ed25519" ]; then | |
| 84 | install -d -o "$RUNNER_USER" -g "$RUNNER_USER" -m 0700 "$RUNNER_DIR/.ssh" | |
| 85 | su -s /bin/sh "$RUNNER_USER" -c "ssh-keygen -q -t ed25519 -N '' -C 'vole-runner@${runner_hostname}' -f $RUNNER_DIR/.ssh/id_ed25519" | |
| 86 | fi | |
| 87 | ||
| 88 | # Pin the instance's host key once. A runner executes repository code, so a | |
| 89 | # host that can answer in the instance's place could send it any job; this | |
| 90 | # file is the boundary, and it is never re-scanned on later starts. | |
| 91 | # | |
| 92 | # The instance may still be starting, or its name may not resolve yet, so | |
| 93 | # this waits for it instead of failing the boot. | |
| 94 | if [ ! -s /etc/vole/runner_known_hosts ]; then | |
| 95 | echo "waiting for $FORGE_HOST on port 2222" | |
| 96 | i=0 | |
| 97 | while [ "$i" -lt 60 ]; do | |
| 98 | if ssh-keyscan -T 5 -p 2222 "$FORGE_HOST" >/tmp/known_hosts 2>/dev/null && [ -s /tmp/known_hosts ]; then | |
| 99 | install -m 0644 /tmp/known_hosts /etc/vole/runner_known_hosts | |
| 100 | echo "pinned the host key of $FORGE_HOST" | |
| 101 | break | |
| 102 | fi | |
| 103 | i=$((i + 1)) | |
| 104 | sleep 10 | |
| 105 | done | |
| 106 | rm -f /tmp/known_hosts | |
| 107 | fi | |
| 108 | ||
| 109 | install -m 0640 /opt/vole-runner/runner.env /etc/vole/runner.env | |
| 110 | systemctl daemon-reload | |
| 111 | systemctl enable vole-runner | |
| 112 | ||
| 113 | if [ -s /etc/vole/runner_known_hosts ]; then | |
| 114 | systemctl restart vole-runner | |
| 115 | else | |
| 116 | echo "$FORGE_HOST did not answer yet; once it does, run: systemctl restart vole-runner" | |
| 117 | fi | |
| 118 | ||
| 119 | runcmd: | |
| 120 | - /usr/local/sbin/vole-runner-first-boot | |
| 121 | ||
| 122 | final_message: >- | |
| 123 | The runner is configured. Register its key as a bot account and allow it on a | |
| 124 | repository; the public key is at ${runner_dir}/.ssh/id_ed25519.pub, and setup | |
| 125 | output is in /var/log/vole-runner-first-boot.log. |