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/vole.yaml +136

@@ -0,0 +1,136 @@
1 #cloud-config
2 # A Vole instance on a fresh Ubuntu or Debian machine, ready in a few minutes.
3 #
4 # Each placeholder below is a value the caller supplies. Terraform renders this
5 # file with templatefile(); deploy/cloud-init/render.sh renders it with
6 # envsubst(). Both read a placeholder as a dollar sign followed by a name in
7 # braces, so shell variables in the script at the bottom are written without
8 # braces and are left alone.
9 #
10 # What this file does, in order: cloud-init installs git and curl, writes the
11 # configuration and the admin's SSH key to /opt/vole-deploy, then runs
12 # vole-first-boot, which installs the package, puts the configuration in place,
13 # creates the instance admin, and starts the service. Nothing here is specific to
14 # a cloud provider; see docs/deploy/ for which machine to give it to.
15
16 hostname: ${hostname}
17 fqdn: ${hostname}
18 manage_etc_hosts: true
19
20 package_update: true
21 packages:
22 - git
23 - ca-certificates
24 - curl
25
26 write_files:
27 # Two configurations are staged and the boot script installs the one that
28 # ${tls_mode} names: "acme" lets Vole obtain its own certificate, "proxy"
29 # expects a TLS proxy in front. Editing the installed /etc/vole/vole.toml is
30 # the normal way to change either afterwards.
31 - path: /opt/vole-deploy/vole.acme.toml
32 permissions: "0644"
33 content: |
34 # Written by cloud-init. This is the whole configuration; see
35 # docs/configuration.md for every setting.
36 data_dir = "/var/lib/vole/data"
37
38 # Vole obtains and renews its own TLS certificate, so no proxy is needed.
39 # Port 80 answers the certificate challenge and redirects to HTTPS.
40 http_addr = ":80"
41 https_addr = ":443"
42 public_url = "https://${hostname}"
43 acme_email = "${admin_email}"
44
45 # The only write transport: Git over SSH, authenticated by key.
46 ssh_addr = "0.0.0.0:2222"
47
48 instance_name = "${instance_name}"
49
50 - path: /opt/vole-deploy/vole.proxy.toml
51 permissions: "0644"
52 content: |
53 # Written by cloud-init. This is the whole configuration; see
54 # docs/configuration.md for every setting.
55 data_dir = "/var/lib/vole/data"
56
57 # Nothing terminates TLS here. Put a proxy in front that keeps the Host
58 # header and forwards the client address, then uncomment the last line.
59 http_addr = "127.0.0.1:8080"
60 public_url = "https://${hostname}"
61 # git_http_client_ip_header = "X-Forwarded-For"
62
63 # The only write transport: Git over SSH, authenticated by key.
64 ssh_addr = "0.0.0.0:2222"
65
66 instance_name = "${instance_name}"
67
68 # Ports below 1024 need one capability from the hardened unit. Only the ACME
69 # configuration binds one.
70 - path: /opt/vole-deploy/bind.conf
71 permissions: "0644"
72 content: |
73 [Service]
74 CapabilityBoundingSet=CAP_NET_BIND_SERVICE
75 AmbientCapabilities=CAP_NET_BIND_SERVICE
76
77 # The first account, and an instance admin. A public key is not a secret, so
78 # this stays readable; the account keeps its own copy in users.db.
79 - path: /opt/vole-deploy/admin.pub
80 permissions: "0644"
81 content: |
82 ${admin_ssh_key}
83
84 - path: /usr/local/sbin/vole-first-boot
85 permissions: "0755"
86 content: |
87 #!/bin/sh
88 # One-time setup for a Vole instance. cloud-init runs this once, on first
89 # boot. It is safe to run again: the package install is skipped when vole
90 # is already there, and the account is only created if it does not exist.
91 set -eu
92
93 ADMIN='${admin_account}'
94 PACKAGE_URL='${vole_package_url}'
95 TLS_MODE='${tls_mode}'
96 LOG=/var/log/vole-first-boot.log
97
98 exec >>"$LOG" 2>&1
99 echo "== vole first boot $(date -u +%Y-%m-%dT%H:%M:%SZ) =="
100
101 # The package creates the service account, /var/lib/vole, and the database
102 # and repository tree the shipped config names. Installing it before the
103 # configuration is written means the shipped conffile is replaced on
104 # purpose, which is the documented way to configure an instance.
105 if ! command -v vole >/dev/null 2>&1; then
106 echo "downloading $PACKAGE_URL"
107 curl -fsSL --retry 5 --retry-delay 2 -o /opt/vole-deploy/vole.deb "$PACKAGE_URL"
108 DEBIAN_FRONTEND=noninteractive apt-get install -y /opt/vole-deploy/vole.deb
109 rm -f /opt/vole-deploy/vole.deb
110 fi
111
112 install -m 0644 "/opt/vole-deploy/vole.$TLS_MODE.toml" /etc/vole/vole.toml
113 if [ "$TLS_MODE" = "acme" ]; then
114 install -d -m 0755 /etc/systemd/system/vole.service.d
115 install -m 0644 /opt/vole-deploy/bind.conf /etc/systemd/system/vole.service.d/bind.conf
116 fi
117 systemctl daemon-reload
118
119 # An account name is permanent, so this creates it once and leaves it alone
120 # on a later run.
121 if su -s /bin/sh vole -c "vole admin --config /etc/vole/vole.toml account show '$ADMIN'" >/dev/null 2>&1; then
122 echo "account $ADMIN already exists"
123 else
124 su -s /bin/sh vole -c "vole admin --config /etc/vole/vole.toml account create '$ADMIN' --key /opt/vole-deploy/admin.pub --role admin"
125 fi
126
127 systemctl enable --now vole
128 echo "vole is running on https://${hostname}"
129
130 runcmd:
131 - /usr/local/sbin/vole-first-boot
132
133 final_message: >-
134 Vole is up. Open https://${hostname} to sign in, or clone
135 ssh://git@${hostname}:2222/ once you have created a repository. Setup output is
136 in /var/log/vole-first-boot.log.