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.

Rewrite the deployment guides as console walkthroughs

942c93cd06345416c580176c1a149f98e7e96cc0 · Keenan Nemetz · 3d ago · parent 51624dc6

Rewrite the deployment guides as console walkthroughs

With the provider configurations gone, the guides are what is left, and
their shape changed: render a file, paste it into a console field, point a
name at the machine.

The index gains the section the Terraform version did not need — first boot
happens once. Editing the pasted file afterwards changes nothing, which is
how cloud-init works rather than a limitation of these pages, so a later
settings change is editing /etc/vole/vole.toml and restarting, and an
upgrade is a package install.

Each cloud page is a table of console fields: image, size, where the user
data goes, and a firewall. The firewall table is load-bearing rather than
advice, because Hetzner and DigitalOcean leave a new machine open and AWS
hands it a security group that allows nothing inbound.

The runner page leads with render.sh runner and keeps the by-hand path under
it; the registration commands are written out now that no output prints them.

docs/deploy/hetzner.md +96−68

@@ -1,100 +1,128 @@
1 1 # Vole on Hetzner Cloud
2 2
3 This creates one server, a firewall, and an SSH key, then turns the machine into
4 a Vole instance on first boot. It takes about three minutes and costs about the
5 price of a small server.
3 One server, configured on its first boot by a file you paste into the console. No
4 API token, nothing to install locally: a rendered file, a server, a firewall, and
5 one DNS record.
6 6
7 7 ## What you need
8 8
9 - A Hetzner Cloud project and an API token for it, with read and write access.
10 Put it in `HCLOUD_TOKEN`.
11 - A DNS name, such as `forge.example.com`. Either point it at the server address
12 the plan prints, or let Terraform create the record if the domain's DNS is a
13 zone in the same Hetzner project.
14 - Your SSH public key, and the URL of a Vole `.deb` for `amd64` (or `arm64` on a
15 CAX server type).
9 - A Hetzner Cloud account and a project.
10 - A DNS name, such as `forge.example.com`.
11 - Your SSH public key.
12 - The URL of a Vole `.deb` for `amd64` (or `arm64` on a CAX server type).
16 13
17 ## Deploy
14 ## Render the first-boot file
18 15
19 From a clone of the Vole repository:
16 From a clone of the Vole repository, on your own computer:
20 17
21 18 ```sh
22 export HCLOUD_TOKEN=... # your project token
23 cd deploy/terraform/hetzner
24 terraform init
25
26 terraform apply \
27 -var domain=forge.example.com \
28 -var "admin_ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)" \
29 -var vole_package_url=https://example.com/r/vole/releases/v0.2.0/artifacts/vole_0.2.0-1_amd64.deb
19 cd deploy/cloud-init
20
21 HOSTNAME=forge.example.com \
22 ADMIN_ACCOUNT=admin \
23 ADMIN_EMAIL=you@example.com \
24 ADMIN_SSH_KEY="$(cat ~/.ssh/id_ed25519.pub)" \
25 VOLE_PACKAGE_URL=https://example.com/r/vole/releases/v0.2.0/artifacts/vole_0.2.0-1_amd64.deb \
26 ./render.sh > user-data.yaml
30 27 ```
31 28
32 Type `yes` when it asks. Then, until DNS answers on the server's address, the
33 certificate cannot be issued; point `forge.example.com` at the `public_ipv4`
34 output (or pass `-var create_dns=true -var dns_zone=example.com` to have
35 Terraform do it).
29 Open `user-data.yaml` before you paste it. It is the whole setup, and it is the
30 last chance to notice a wrong account name: names are permanent.
36 31
37 ## What you get
32 ## Create the server
38 33
39 ```text
40 web_url = "https://forge.example.com"
41 git_remote = "ssh://git@forge.example.com:2222/"
42 public_ipv4 = "203.0.113.10"
43 admin_account = "admin"
44 server_login = "ssh root@203.0.113.10"
45 ```
34 In the Hetzner console, **Add Server**:
35
36 | Field | What to choose |
37 |---|---|
38 | Location | wherever your team is |
39 | Image | Ubuntu 24.04 (Debian 13 works too) |
40 | Type | CX22 — 2 vCPU, 4 GB, 40 GB. Raise it if you expect big pushes or LFS |
41 | Networking | public IPv4 |
42 | SSH key | yours, the same one you put in the file |
43 | Backups | on. It is a snapshot, not a backup of the instance — see [Backups](README.md#backups-are-still-your-job) |
44 | **Cloud config** | paste the contents of `user-data.yaml` |
45
46 Before or after creating it, add a **Firewall** in the same project and apply it to
47 the server. Hetzner leaves a new server open if you do not:
48
49 | Direction | Protocol | Port | Source |
50 |---|---|---|---|
51 | in | tcp | 22 | your team's addresses, for shell access |
52 | in | tcp | 2222 | your team's addresses. This is Git over SSH, the write transport |
53 | in | tcp | 80 | anywhere, for the certificate challenge |
54 | in | tcp | 443 | anywhere |
55 | out | any | any | anywhere, so the server can fetch its package and certificate |
56
57 Restricting 2222 to your team is the cheapest way to keep the write side private.
58 If you leave it open, Vole still authenticates every connection by key.
46 59
47 Open `web_url` after DNS resolves. The account named by `admin_account` is an
48 instance admin and has no passkey yet; issue one from your laptop:
60 ## Point the name at it
61
62 Create an `A` record for `forge.example.com` pointing at the server's IPv4
63 address. Vole cannot obtain a certificate for a name that does not resolve to it
64 yet, and it retries, so a record created a few minutes after the server is fine.
65
66 ## Check it works
67
68 Open `https://forge.example.com`. The first request may take a few seconds while
69 the certificate is issued; if it fails, wait a minute and reload.
70
71 Issue a passkey for the admin account from your laptop:
49 72
50 73 ```sh
51 74 vole admin --remote ssh://git@forge.example.com:2222 account invite admin
52 75 ```
53 76
77 Then create a repository, and clone it:
78
79 ```sh
80 vole admin --remote ssh://git@forge.example.com:2222 repo create demo --owner admin
81 vole admin --remote ssh://git@forge.example.com:2222 acl set-default-role demo read
82 git clone ssh://git@forge.example.com:2222/demo.git
83 ```
84
85 ## Adding a CI runner
86
87 Create a second server the same way, but give it the runner's file:
88
89 ```sh
90 ./render.sh runner > runner-user-data.yaml # FORGE_HOST=forge.example.com and VOLE_PACKAGE_URL
91 ```
92
93 It needs no inbound ports except 22. It needs one more step per repository before
94 it takes work; see [CI runners](runner.md).
95
54 96 ## The settings worth changing
55 97
56 Everything else has a working default. Pass these with `-var` or in a
57 `terraform.tfvars` file beside `main.tf`.
98 The values you pass to `render.sh`. Everything else has a working default.
58 99
59 | Setting | Default | What it does |
100 | Variable | Default | What it does |
60 101 |---|---|---|
61 | `ssh_admin_cidrs` | everything | Addresses that may reach port 22 (server login) and 2222 (Git over SSH). Setting this to your team's range is the cheapest way to keep the write side private |
62 | `server_type` | `cx22` | 2 vCPU, 4 GB, 40 GB. `cx32` doubles the CPU and disk; `cax11` is the same size on arm64, and then the package URL must be the `arm64` one |
63 | `location` | `fsn1` | Falkenstein. `nbg1` Nuremberg, `hel1` Helsinki, `ash` Ashburn, `hil` Hillsboro, `sin` Singapore |
64 | `name` | `vole` | Name of the server, firewall, and SSH key in the project |
65 | `enable_backups` | `true` | Daily snapshots kept for a week, for about 20% of the server price |
66 | `tls_mode` | `acme` | `proxy` leaves TLS to a proxy you run instead, and serves plain HTTP on loopback |
67 | `instance_name` | `Vole` | What the landing page calls this forge |
68 | `runner` | `false` | Create a second machine that runs CI jobs. See [CI runners](runner.md): it needs one more step per repository before it takes work |
69 | `runner_type` | `cx22` | Server type for that machine. Builds want memory, so raise this before the forge's own type |
70 | `runner_labels` | `["linux"]` | What the runner advertises; a job with `runs-on` only comes to a machine carrying every label it asks for |
71 | `runner_network` | `bridge` | Whether a job that asks for a network gets one. Jobs that fetch dependencies need it |
72 | `protect_instance` | `true` | Refuses deletion and rebuild through the API, so `terraform destroy` needs `-var protect_instance=false` |
73
74 `terraform apply` is safe to run again: it reports no changes. It does not
75 upgrade Vole, and it does not touch the machine's data.
76
77 With `-var runner=true`, `terraform output runner_setup` prints the commands that
78 let the second machine take jobs. It needs one step per repository before it
79 receives any work; see [CI runners](runner.md).
102 | `HOSTNAME` | required | The name the instance answers on. It becomes `public_url`, and passkeys bind to it |
103 | `ADMIN_ACCOUNT` | required | The first account, made an instance admin. Permanent |
104 | `ADMIN_SSH_KEY` | required | That account's public key |
105 | `VOLE_PACKAGE_URL` | required | The `.deb` for the machine's architecture |
106 | `ADMIN_EMAIL` | empty | Contact address for the certificate authority |
107 | `INSTANCE_NAME` | `Vole` | What the landing page calls this forge |
108 | `TLS_MODE` | `acme` | `proxy` leaves TLS to a proxy you run, and serves plain HTTP on loopback |
80 109
81 110 ## If something is not right
82 111
83 112 - **The browser shows a certificate warning.** DNS is not answering with the
84 server's address yet, or ports 80 and 443 are not reachable. Check with
85 `dig +short forge.example.com`, then look at `journalctl -u vole | grep -i acme`
86 on the machine.
87 - **The machine is up but the forge is not.** First-boot output is in
88 `/var/log/vole-first-boot.log`, and the service log is `journalctl -u vole`.
89 The usual cause is a package URL that 404s or is for the other architecture.
90 - **`terraform apply` fails on credentials.** `HCLOUD_TOKEN` is unset or the token
91 is read-only.
92 - **Pushing is refused.** Port 2222 has to be reachable from where you push, and
93 the key has to belong to an account. `vole whoami --remote
94 ssh://git@forge.example.com:2222` says which account your key is.
113 server's address yet, or ports 80 and 443 are blocked by the firewall. Check
114 `dig +short forge.example.com`, then `journalctl -u vole | grep -i acme`.
115 - **The server is up but the forge is not.** First-boot output is in
116 `/var/log/vole-first-boot.log`; the service log is `journalctl -u vole`. The
117 usual cause is a package URL that 404s or is for the other architecture.
118 - **Nothing listens on 2222.** The firewall is not applied to the server, or its
119 source addresses do not include where you are pushing from.
120 - **Pushing is refused.** `vole whoami --remote ssh://git@forge.example.com:2222`
121 says which account your key is; if it is not the one you expected, the key was
122 not the one in `user-data.yaml`.
95 123
96 124 ## Next
97 125
98 - [Deploying Vole](README.md) — backups, upgrades, and what first boot does
126 - [Deploying Vole](README.md) — what first boot does, backups, and upgrades
99 127 - [Packages](../packaging.md) — the `.deb`, the unit file, and upgrades on the machine
100 128 - [Accounts and access](../accounts-and-access.md) — the first accounts, roles, and passkeys