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/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Ship the runner's unit and let a deployment create a runner machine

d0d4239f3f7236197c87a752d7965de238430f98 · Keenan Nemetz · 3d ago · parent dbae2e01

Ship the runner's unit and let a deployment create a runner machine

The package installs one binary that is the forge, the runner, and the CLI,
so it now carries a second unit. It is not enabled, and it refuses to start
with a message until an operator fills in /etc/vole/runner.env: who runs
builds on a machine is that machine owner's decision.

The unit runs as a dedicated account, because rootless podman needs a home
directory and a range of subordinate ids, and because a job should not find
the forge's own account on the same disk. The arguments live in that one
file rather than in the unit, split at spaces by systemd, so a single unit
serves every machine.

Each Terraform configuration takes runner=true and creates that machine
beside the forge: its own firewall, no backups (its checkouts and caches are
copies), the same key for a shell, and Cloud-init that installs the package,
creates the account and its subuids, generates the runner's key, pins the
instance's host key once, and enables the unit. The runner_setup output
prints the three commands that let it take work, filled in with the
addresses this apply created.

The first-boot file cannot register the runner: the allowlist is per
repository, and at first boot there are no repositories. That step stays
with an instance admin, and the docs say so.

deploy/cloud-init/render.sh +81−49

@@ -1,67 +1,99 @@
1 1 #!/bin/sh
2 # Render deploy/cloud-init/vole.yaml for one instance, for the terminals that do
3 # not use Terraform.
2 # Render a first-boot file for one machine, for the terminals that do not use
3 # Terraform.
4 4 #
5 # Usage:
5 # ./render.sh > user-data.yaml # the forge
6 # ./render.sh runner > runner-user-data.yaml # a CI runner
6 7 #
7 # HOSTNAME=forge.example.com \
8 # ADMIN_ACCOUNT=you \
9 # ADMIN_EMAIL=you@example.com \
10 # ADMIN_SSH_KEY="$(cat ~/.ssh/id_ed25519.pub)" \
11 # VOLE_PACKAGE_URL=https://example.com/r/vole/releases/v0.2.0/artifacts/vole_0.2.0-1_amd64.deb \
12 # ./render.sh > user-data.yaml
13 #
14 # Unset values fall back to the defaults below. INSTANCE_NAME defaults to "Vole"
15 # and TLS_MODE to "acme"; set TLS_MODE=proxy to leave TLS to a proxy you run.
16 #
17 # The output is what a cloud provider's "user data" field wants. See
18 # docs/deploy/vps.md for providers that accept it through their web console.
8 # Values come from the environment. Anything unset falls back to the default
9 # below. The output goes into a cloud provider's "user data" field. See
10 # docs/deploy/vps.md and docs/deploy/runner.md.
19 11 set -eu
20 12
21 13 here="$(cd "$(dirname "$0")" && pwd)"
14 what="${1:-vole}"
15
16 if ! command -v envsubst >/dev/null 2>&1; then
17 echo "envsubst is missing; install gettext (Debian and Ubuntu: apt install gettext-base)" >&2
18 exit 2
19 fi
20
21 # render <template> <placeholder names...>
22 #
23 # Only the named placeholders are substituted. A name outside the list is a typo,
24 # and envsubst would leave it in the output as literal text, so the list is the
25 # contract between this script and the template.
26 render() {
27 template="$1"
28 shift
29 list=""
30 for name in "$@"; do
31 list="$list\${$name} "
32 done
33 rendered="$(envsubst "$list" <"$here/$template")"
34 if printf '%s\n' "$rendered" | grep -q '\${'; then
35 echo "unsubstituted placeholder in $template:" >&2
36 printf '%s\n' "$rendered" | grep -n '\${' >&2
37 exit 1
38 fi
39 printf '%s\n' "$rendered"
40 }
22 41
23 : "${HOSTNAME:?set HOSTNAME to the DNS name the instance will answer on}"
24 : "${ADMIN_ACCOUNT:?set ADMIN_ACCOUNT to the account name to create}"
25 : "${ADMIN_SSH_KEY:?set ADMIN_SSH_KEY to the public key of that account}"
26 : "${VOLE_PACKAGE_URL:?set VOLE_PACKAGE_URL to the .deb for this machine's architecture}"
27 : "${ADMIN_EMAIL:=}"
28 : "${INSTANCE_NAME:=Vole}"
29 : "${TLS_MODE:=acme}"
42 case "$what" in
43 vole)
44 : "${HOSTNAME:?set HOSTNAME to the DNS name the instance will answer on}"
45 : "${ADMIN_ACCOUNT:?set ADMIN_ACCOUNT to the account name to create}"
46 : "${ADMIN_SSH_KEY:?set ADMIN_SSH_KEY to the public key of that account}"
47 : "${VOLE_PACKAGE_URL:?set VOLE_PACKAGE_URL to the .deb for this machine's architecture}"
48 : "${ADMIN_EMAIL:=}"
49 : "${INSTANCE_NAME:=Vole}"
50 : "${TLS_MODE:=acme}"
30 51
31 case "$TLS_MODE" in
52 case "$TLS_MODE" in
32 53 acme | proxy) ;;
33 54 *)
34 55 echo "TLS_MODE must be acme or proxy, got $TLS_MODE" >&2
35 56 exit 2
36 57 ;;
37 esac
58 esac
38 59
39 if ! command -v envsubst >/dev/null 2>&1; then
40 echo "envsubst is missing; install gettext (Debian and Ubuntu: apt install gettext-base)" >&2
41 exit 2
42 fi
60 export hostname="$HOSTNAME"
61 export admin_account="$ADMIN_ACCOUNT"
62 export admin_ssh_key="$ADMIN_SSH_KEY"
63 export admin_email="$ADMIN_EMAIL"
64 export instance_name="$INSTANCE_NAME"
65 export tls_mode="$TLS_MODE"
66 export vole_package_url="$VOLE_PACKAGE_URL"
43 67
44 # Only the placeholders are substituted. A name outside this list is a typo, and
45 # envsubst would silently leave it in the output as literal text, so the list is
46 # the contract between this script and the template.
47 export hostname="$HOSTNAME"
48 export admin_account="$ADMIN_ACCOUNT"
49 export admin_ssh_key="$ADMIN_SSH_KEY"
50 export admin_email="$ADMIN_EMAIL"
51 export instance_name="$INSTANCE_NAME"
52 export tls_mode="$TLS_MODE"
53 export vole_package_url="$VOLE_PACKAGE_URL"
68 render vole.yaml hostname admin_account admin_ssh_key admin_email instance_name tls_mode vole_package_url
69 ;;
70 runner)
71 : "${FORGE_HOST:?set FORGE_HOST to the instance's DNS name}"
72 : "${VOLE_PACKAGE_URL:?set VOLE_PACKAGE_URL to the .deb for this machine's architecture}"
73 : "${RUNNER_HOSTNAME:=vole-runner}"
74 : "${RUNNER_RUNTIME:=podman}"
75 : "${RUNNER_NETWORK:=bridge}"
76 : "${RUNNER_DIR:=/var/lib/vole-runner}"
77 # Labels a job's runs-on can ask for, space separated: "linux amd64".
78 : "${RUNNER_LABEL_NAMES:=linux}"
54 79
55 placeholder_list='${hostname} ${admin_account} ${admin_ssh_key} ${admin_email} ${instance_name} ${tls_mode} ${vole_package_url}'
80 labels=""
81 for name in $RUNNER_LABEL_NAMES; do
82 labels="$labels--label $name "
83 done
56 84
57 rendered="$(envsubst "$placeholder_list" <"$here/vole.yaml")"
85 export runner_hostname="$RUNNER_HOSTNAME"
86 export forge_host="$FORGE_HOST"
87 export runner_runtime="$RUNNER_RUNTIME"
88 export runner_network="$RUNNER_NETWORK"
89 export runner_dir="$RUNNER_DIR"
90 export runner_labels="$labels"
91 export vole_package_url="$VOLE_PACKAGE_URL"
58 92
59 # A placeholder means the caller's value did not arrive, and the instance would
60 # boot with the literal text in its configuration.
61 if printf '%s\n' "$rendered" | grep -q '\${'; then
62 echo "unsubstituted placeholder in the rendered file:" >&2
63 printf '%s\n' "$rendered" | grep -n '\${' >&2
64 exit 1
65 fi
66
67 printf '%s\n' "$rendered"
93 render runner.yaml runner_hostname forge_host runner_runtime runner_network runner_dir runner_labels vole_package_url
94 ;;
95 *)
96 echo "unknown template $what; use vole or runner" >&2
97 exit 2
98 ;;
99 esac