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.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 18m ago
4b8c9e1d
| 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 | # |
| 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. |
| 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. |
| 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} |
| 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. |
| 126 |