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/aws/variables.tf −143
| @@ -1,143 +0,0 @@ | |||
| 1 | variable "name" { | ||
| 2 | description = "Name for the instance, key pair, and security group." | ||
| 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 "protect_instance" { | ||
| 57 | description = "Refuse to terminate the instance from the API while true. A machine holding a team's repositories should not disappear because of a mistyped command." | ||
| 58 | type = bool | ||
| 59 | default = true | ||
| 60 | } | ||
| 61 | |||
| 62 | variable "region" { | ||
| 63 | description = "AWS region, as in eu-central-1 or us-east-1." | ||
| 64 | type = string | ||
| 65 | default = "eu-central-1" | ||
| 66 | } | ||
| 67 | |||
| 68 | variable "instance_type" { | ||
| 69 | description = "EC2 instance type. t3.medium (2 vCPU, 4 GB) is enough for a small team. Graviton types need an arm64 image and an arm64 package." | ||
| 70 | type = string | ||
| 71 | default = "t3.medium" | ||
| 72 | } | ||
| 73 | |||
| 74 | variable "disk_gb" { | ||
| 75 | description = "Root volume size in GiB. Repositories, LFS objects, and CI logs live on it; 40 is a reasonable start." | ||
| 76 | type = number | ||
| 77 | default = 40 | ||
| 78 | } | ||
| 79 | |||
| 80 | variable "create_dns" { | ||
| 81 | description = "Create the A record in a Route 53 hosted zone this account already holds. 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 = "Hosted zone name for create_dns, as in example.com. Must match the zone exactly." | ||
| 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"] | ||
| 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 = "EC2 instance type for the runner. Builds want memory, so this is worth raising before the instance's own type." | ||
| 112 | type = string | ||
| 113 | default = "t3.medium" | ||
| 114 | } | ||
| 115 | |||
| 116 | variable "runner_disk_gb" { | ||
| 117 | description = "Root volume size for the runner in GiB. Container images and build caches fill this, not the repositories." | ||
| 118 | type = number | ||
| 119 | default = 40 | ||
| 120 | } | ||
| 121 | |||
| 122 | variable "runner_labels" { | ||
| 123 | description = "Labels this runner advertises, which a job's runs-on asks for." | ||
| 124 | type = list(string) | ||
| 125 | default = ["linux"] | ||
| 126 | } | ||
| 127 | |||
| 128 | variable "runner_network" { | ||
| 129 | 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." | ||
| 130 | type = string | ||
| 131 | default = "bridge" | ||
| 132 | |||
| 133 | validation { | ||
| 134 | condition = contains(["none", "bridge"], var.runner_network) | ||
| 135 | error_message = "runner_network must be none or bridge." | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | variable "runner_cloud_init_file" { | ||
| 140 | description = "Override the runner's user data file. Empty uses deploy/cloud-init/runner.yaml from this repository." | ||
| 141 | type = string | ||
| 142 | default = "" | ||
| 143 | } | ||