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.

Drop the Terraform configurations; cloud-init is the deployment path

51624dc663738e70cc37b8ac5e232841fd49aaf8 · Keenan Nemetz · 3d ago · parent e21f6c5e

Drop the Terraform configurations; cloud-init is the deployment path

A Vole instance is one package, one configuration file, and one directory.
The first-boot file that installs the package and writes the configuration
is that same work described once, and it needs no provider, no state file,
and no cloud token with write access to an account. What Terraform did
outside the machine — firewall, address, DNS record — is three fields in a
console the operator already has open.

So the three configurations go, and with them what existed only to check
them: the pinned Terraform download in the verify image, the `fmt -check`
step, the .terraform entries in the ignore file, and the directory skip in
the link checker. The deployment check now needs python3 and PyYAML and
nothing else — no network, no credentials.

deploy/README.md keeps the reasoning, so the next person tempted to add a
provider back sees what it would buy and what it would cost.

The templates' header comments no longer name a renderer that is gone:
render.sh fills them in, and check-deploy.py renders them the same way.

deploy/terraform/digitalocean/variables.tf −137

@@ -1,137 +0,0 @@
1 variable "name" {
2 description = "Name for the droplet, firewall, and SSH key."
3 type = string
4 default = "vole"
5 }
6
7 variable "domain" {
8 description = "DNS name the instance answers on, as in forge.example.com. It becomes public_url, and passkeys are bound to it, so it cannot change later without re-enrolling everyone."
9 type = string
10
11 validation {
12 condition = !startswith(var.domain, "http://") && !startswith(var.domain, "https://")
13 error_message = "domain is a bare host name: forge.example.com, not https://forge.example.com."
14 }
15 }
16
17 variable "admin_account" {
18 description = "Name of the first account, made an instance admin. Account names are permanent."
19 type = string
20 default = "admin"
21 }
22
23 variable "admin_ssh_public_key" {
24 description = "Public key of the first account, as in file contents: ssh-ed25519 AAAAC3... you@host."
25 type = string
26 }
27
28 variable "admin_email" {
29 description = "Contact address given to the certificate authority. Optional but recommended when ACME is on."
30 type = string
31 default = ""
32 }
33
34 variable "instance_name" {
35 description = "Name the landing page shows."
36 type = string
37 default = "Vole"
38 }
39
40 variable "tls_mode" {
41 description = "acme lets Vole obtain its own certificate on ports 80 and 443 and needs DNS pointing here. proxy leaves TLS to a proxy in front and serves plain HTTP on loopback."
42 type = string
43 default = "acme"
44
45 validation {
46 condition = contains(["acme", "proxy"], var.tls_mode)
47 error_message = "tls_mode must be acme or proxy."
48 }
49 }
50
51 variable "vole_package_url" {
52 description = "URL of the .deb for this machine's architecture, from a Vole release page: https://.../r/vole/releases/v0.2.0/artifacts/vole_0.2.0-1_amd64.deb"
53 type = string
54 }
55
56 variable "region" {
57 description = "DigitalOcean region, as in nyc3, sfo3, ams3, fra1, blr1, syd1."
58 type = string
59 default = "nyc3"
60 }
61
62 variable "size" {
63 description = "Droplet size slug. s-2vcpu-4gb is enough for a small team; s-4vcpu-8gb doubles the CPU, memory, and disk."
64 type = string
65 default = "s-2vcpu-4gb"
66 }
67
68 variable "image" {
69 description = "Ubuntu or Debian image slug. The cloud-init file runs on either."
70 type = string
71 default = "ubuntu-24-04-x64"
72 }
73
74 variable "enable_backups" {
75 description = "Weekly DigitalOcean backups. Costs about 20% of the droplet price, and is not a substitute for an off-site copy of the instance."
76 type = bool
77 default = true
78 }
79
80 variable "create_dns" {
81 description = "Create the A record in a DigitalOcean domain this account already owns. Leave false to point your own DNS at the address output instead."
82 type = bool
83 default = false
84 }
85
86 variable "dns_zone" {
87 description = "Zone for create_dns, as in example.com. The record is created for the domain variable."
88 type = string
89 default = ""
90 }
91
92 variable "ssh_admin_cidrs" {
93 description = "Addresses allowed to reach port 22 (server login) and port 2222 (Git over SSH). Restrict these to your team if you can."
94 type = list(string)
95 default = ["0.0.0.0/0", "::/0"]
96 }
97
98 variable "cloud_init_file" {
99 description = "Override the user data file. Empty uses deploy/cloud-init/vole.yaml from this repository."
100 type = string
101 default = ""
102 }
103
104 variable "runner" {
105 description = "Create a second machine that runs CI jobs. See docs/deploy/runner.md: an admin has to register its key and allow it on a repository before it receives any work."
106 type = bool
107 default = false
108 }
109
110 variable "runner_type" {
111 description = "Droplet size slug for the runner. Builds want memory, so this is worth raising before the instance's own size."
112 type = string
113 default = "s-2vcpu-4gb"
114 }
115
116 variable "runner_labels" {
117 description = "Labels this runner advertises, which a job's runs-on asks for."
118 type = list(string)
119 default = ["linux"]
120 }
121
122 variable "runner_network" {
123 description = "none gives a job no network even if it asks; bridge gives one to a job that asks. Jobs that fetch dependencies need bridge."
124 type = string
125 default = "bridge"
126
127 validation {
128 condition = contains(["none", "bridge"], var.runner_network)
129 error_message = "runner_network must be none or bridge."
130 }
131 }
132
133 variable "runner_cloud_init_file" {
134 description = "Override the runner's user data file. Empty uses deploy/cloud-init/runner.yaml from this repository."
135 type = string
136 default = ""
137 }