Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 18m ago 4b8c9e1d
history blame raw 5.3 KiB
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
14hostname: ${runner_hostname}
15fqdn: ${runner_hostname}
16manage_etc_hosts: true
17
18package_update: true
19packages:
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
31write_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
119runcmd:
120 - /usr/local/sbin/vole-runner-first-boot
121
122final_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