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.

Turn a fresh machine into an instance on first boot

04e063ca5d73b8f57f45d7c8bb07b0fdaa91b5b8 · Keenan Nemetz · 3d ago · parent ea21f923

Turn a fresh machine into an instance on first boot

A new instance is one package, one configuration, one directory, and one
account. Assembling that by hand is where a first deployment goes wrong:
the capability the hardened unit needs for ports 80 and 443, the order that
makes the package's own init harmless, and the fact that the first instance
admin has to be granted on the host.

deploy/cloud-init/vole.yaml is that assembly. It stages two configurations,
one that lets Vole hold its own certificate and one that leaves TLS to a
proxy, then installs the package, writes the configuration, creates the
instance admin, and starts the service. Running it again changes nothing.

render.sh renders it for providers that take user data without Terraform,
using the placeholder list as the contract with the template.

The three Terraform configurations create the machine, its firewall, its
address, and optionally the DNS record, and hand it that same file. They are
root modules: init and apply, three variables, no other file to edit.

First boot is not configuration management. apply does not upgrade an
instance or touch its data, because users.db migrates in place and cannot be
rolled back.

deploy/cloud-init/render.sh +67

@@ -0,0 +1,67 @@
1 #!/bin/sh
2 # Render deploy/cloud-init/vole.yaml for one instance, for the terminals that do
3 # not use Terraform.
4 #
5 # Usage:
6 #
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.
19 set -eu
20
21 here="$(cd "$(dirname "$0")" && pwd)"
22
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}"
30
31 case "$TLS_MODE" in
32 acme | proxy) ;;
33 *)
34 echo "TLS_MODE must be acme or proxy, got $TLS_MODE" >&2
35 exit 2
36 ;;
37 esac
38
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
43
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"
54
55 placeholder_list='${hostname} ${admin_account} ${admin_ssh_key} ${admin_email} ${instance_name} ${tls_mode} ${vole_package_url}'
56
57 rendered="$(envsubst "$placeholder_list" <"$here/vole.yaml")"
58
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"