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/digitalocean.md +96−70

@@ -1,103 +1,129 @@
1 1 # Vole on DigitalOcean
2 2
3 This creates one droplet, a firewall, and an SSH key, then turns the machine into
4 a Vole instance on first boot. It takes about three minutes.
3 One droplet, 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 droplet, a firewall, and
5 one DNS record.
5 6
6 7 ## What you need
7 8
8 - A DigitalOcean API token with read and write access. Put it in
9 `DIGITALOCEAN_TOKEN`, or in `DIGITALOCEAN_TOKEN` in your Terraform
10 environment, which is the same thing.
11 - A DNS name, such as `forge.example.com`. Either point it at the droplet's
12 address, or let Terraform add the name to DigitalOcean's DNS and create the
13 record.
14 - Your SSH public key, and the URL of a Vole `.deb` for `amd64`.
9 - A DigitalOcean account.
10 - A DNS name, such as `forge.example.com`.
11 - Your SSH public key.
12 - The URL of a Vole `.deb` for `amd64`.
15 13
16 ## Deploy
14 ## Render the first-boot file
17 15
18 From a clone of the Vole repository:
16 From a clone of the Vole repository, on your own computer:
19 17
20 18 ```sh
21 export DIGITALOCEAN_TOKEN=... # your API token
22 cd deploy/terraform/digitalocean
23 terraform init
24
25 terraform apply \
26 -var domain=forge.example.com \
27 -var "admin_ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)" \
28 -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
29 27 ```
30 28
31 Type `yes` when it asks. Then point `forge.example.com` at the `public_ipv4`
32 output, or pass `-var create_dns=true -var dns_zone=example.com` to have Terraform
33 create the record in DigitalOcean's DNS.
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.
34 31
35 ## What you get
32 ## Create the droplet
36 33
37 ```text
38 web_url = "https://forge.example.com"
39 git_remote = "ssh://git@forge.example.com:2222/"
40 public_ipv4 = "203.0.113.10"
41 admin_account = "admin"
42 server_login = "ssh root@203.0.113.10"
43 ```
34 In the control panel, **Create Droplet**:
35
36 | Field | What to choose |
37 |---|---|
38 | Region | wherever your team is |
39 | Image | Ubuntu 24.04 (LTS) x64. Debian 13 works too |
40 | Size | Basic, 2 vCPU / 4 GB / 80 GB. Raise it if you expect big pushes or LFS |
41 | Authentication | SSH key — the same one you put in the file |
42 | **Advanced options → user data** | paste the contents of `user-data.yaml` |
43 | Backups | on. It is a snapshot, not a backup of the instance — see [Backups](README.md#backups-are-still-your-job) |
44 | Monitoring | on. The free graphs are the quickest way to see a build filling the disk |
45
46 Then add a **Cloud Firewall** under Networking and apply it to the droplet.
47 DigitalOcean leaves a droplet 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 droplet 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.
44 59
45 Open `web_url` after DNS resolves. The account named by `admin_account` is an
46 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 droplet's IPv4
63 address, in DigitalOcean's DNS or wherever the name lives. Vole cannot obtain a
64 certificate for a name that does not resolve to it yet, and it retries, so a record
65 created a few minutes after the droplet is fine.
66
67 ## Check it works
68
69 Open `https://forge.example.com`. The first request may take a few seconds while
70 the certificate is issued; if it fails, wait a minute and reload.
71
72 Issue a passkey for the admin account from your laptop:
47 73
48 74 ```sh
49 75 vole admin --remote ssh://git@forge.example.com:2222 account invite admin
50 76 ```
51 77
78 Then create a repository, and clone it:
79
80 ```sh
81 vole admin --remote ssh://git@forge.example.com:2222 repo create demo --owner admin
82 vole admin --remote ssh://git@forge.example.com:2222 acl set-default-role demo read
83 git clone ssh://git@forge.example.com:2222/demo.git
84 ```
85
86 ## Adding a CI runner
87
88 Create a second droplet the same way, but give it the runner's file:
89
90 ```sh
91 ./render.sh runner > runner-user-data.yaml # FORGE_HOST=forge.example.com and VOLE_PACKAGE_URL
92 ```
93
94 It needs no inbound ports except 22. It needs one more step per repository before
95 it takes work; see [CI runners](runner.md).
96
52 97 ## The settings worth changing
53 98
54 Everything else has a working default. Pass these with `-var` or in a
55 `terraform.tfvars` file beside `main.tf`.
99 The values you pass to `render.sh`. Everything else has a working default.
56 100
57 | Setting | Default | What it does |
101 | Variable | Default | What it does |
58 102 |---|---|---|
59 | `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 |
60 | `size` | `s-2vcpu-4gb` | 2 vCPU, 4 GB, 80 GB. `s-4vcpu-8gb` doubles all three if builds run on this machine |
61 | `region` | `nyc3` | `sfo3`, `ams3`, `fra1`, `blr1`, `syd1`, and others |
62 | `name` | `vole` | Name of the droplet, firewall, and SSH key |
63 | `enable_backups` | `true` | Weekly droplet backups, for about 20% of the droplet price |
64 | `tls_mode` | `acme` | `proxy` leaves TLS to a proxy you run instead, and serves plain HTTP on loopback |
65 | `instance_name` | `Vole` | What the landing page calls this forge |
66 | `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 |
67 | `runner_type` | `s-2vcpu-4gb` | Droplet size for that machine. Builds want memory, so raise this before the forge's own size |
68 | `runner_labels` | `["linux"]` | What the runner advertises; a job with `runs-on` only comes to a machine carrying every label it asks for |
69 | `runner_network` | `bridge` | Whether a job that asks for a network gets one. Jobs that fetch dependencies need it |
70
71 The droplet reports CPU, memory, and disk to DigitalOcean's graphs, which is the
72 quickest way to see that a build filled the disk.
73
74 `terraform apply` is safe to run again: it reports no changes. It does not upgrade
75 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).
80
81 DigitalOcean has no delete protection on droplets, so `terraform destroy` does
82 what it says. The instance's repositories and accounts are on that droplet.
103 | `HOSTNAME` | required | The name the instance answers on. It becomes `public_url`, and passkeys bind to it |
104 | `ADMIN_ACCOUNT` | required | The first account, made an instance admin. Permanent |
105 | `ADMIN_SSH_KEY` | required | That account's public key |
106 | `VOLE_PACKAGE_URL` | required | The `.deb` for the machine's architecture |
107 | `ADMIN_EMAIL` | empty | Contact address for the certificate authority |
108 | `INSTANCE_NAME` | `Vole` | What the landing page calls this forge |
109 | `TLS_MODE` | `acme` | `proxy` leaves TLS to a proxy you run, and serves plain HTTP on loopback |
83 110
84 111 ## If something is not right
85 112
86 113 - **The browser shows a certificate warning.** DNS is not answering with the
87 droplet's address yet, or ports 80 and 443 are not reachable. Check with
88 `dig +short forge.example.com`, then look at `journalctl -u vole | grep -i acme`
89 on the machine.
114 droplet's address yet, or ports 80 and 443 are blocked by the firewall. Check
115 `dig +short forge.example.com`, then `journalctl -u vole | grep -i acme`.
90 116 - **The droplet is up but the forge is not.** First-boot output is in
91 `/var/log/vole-first-boot.log`, and the service log is `journalctl -u vole`.
92 The usual cause is a package URL that 404s or is for the other architecture.
93 - **`terraform apply` fails on credentials.** `DIGITALOCEAN_TOKEN` is unset or the
94 token is read-only.
95 - **Pushing is refused.** Port 2222 has to be reachable from where you push, and
96 the key has to belong to an account. `vole whoami --remote
97 ssh://git@forge.example.com:2222` says which account your key is.
117 `/var/log/vole-first-boot.log`; the service log is `journalctl -u vole`. The
118 usual cause is a package URL that 404s or is for the other architecture.
119 - **Nothing listens on 2222.** The firewall is not applied to the droplet, or its
120 source addresses do not include where you are pushing from.
121 - **Pushing is refused.** `vole whoami --remote ssh://git@forge.example.com:2222`
122 says which account your key is; if it is not the one you expected, the key was
123 not the one in `user-data.yaml`.
98 124
99 125 ## Next
100 126
101 - [Deploying Vole](README.md) — backups, upgrades, and what first boot does
127 - [Deploying Vole](README.md) — what first boot does, backups, and upgrades
102 128 - [Packages](../packaging.md) — the `.deb`, the unit file, and upgrades on the machine
103 129 - [Accounts and access](../accounts-and-access.md) — the first accounts, roles, and passkeys