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.
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/README.md +27−21
| @@ -5,36 +5,42 @@ The files here put a Vole instance on a fresh machine. The guides are in | |||
| 5 | 5 | ||
| 6 | 6 | | Path | What it is | | |
| 7 | 7 | |---|---| | |
| 8 | | `cloud-init/vole.yaml` | The first-boot file. Installs the package, writes the configuration, creates the instance admin, starts the service. Used by every path below | | ||
| 9 | | `cloud-init/runner.yaml` | The CI runner's first-boot file: rootless Podman, an unprivileged account, a pinned host key, and the unit enabled | | ||
| 10 | | `cloud-init/render.sh` | Renders either first-boot file for a machine you create by hand, with no Terraform | | ||
| 11 | | `terraform/hetzner`, `terraform/digitalocean`, `terraform/aws` | One complete Terraform configuration per cloud: machine, firewall, address, optional DNS record, and `-var runner=true` for a second machine that runs CI jobs. Each is used as a root module | | ||
| 8 | | `cloud-init/vole.yaml` | The forge's first-boot file. Installs the package, writes the configuration, creates the instance admin, starts the service | | ||
| 9 | | `cloud-init/runner.yaml` | The CI runner's first-boot file. Rootless Podman, an unprivileged account, a pinned host key, and the unit enabled | | ||
| 10 | | `cloud-init/render.sh` | Fills either file in from the environment, for pasting into a provider's user-data field | | ||
| 12 | 11 | | `docker/Dockerfile` | The server in one image, built from this repository | | |
| 13 | 12 | | `docker/compose.yaml` | That image, with a volume for the instance and a health check | | |
| 14 | 13 | | `docker/vole.toml` | The configuration the image starts with, and the file to copy for a deployment | | |
| 15 | 14 | | `docker/entrypoint.sh` | Runs `vole admin init` once, then the command you asked for | | |
| 16 | 15 | ||
| 16 | Cloud-init is the whole deployment story, and it is deliberately the only one. | ||
| 17 | Vole is one package, one configuration file, and one directory, so a first-boot | ||
| 18 | file that installs the package and writes the configuration is the same work a | ||
| 19 | Terraform configuration would describe — without a provider, a state file, an API | ||
| 20 | token with write access to the account, or a second thing to keep upgraded. What | ||
| 21 | Terraform would do outside the machine — a firewall, an address, a DNS record — | ||
| 22 | is three fields in the console you already have open. | ||
| 23 | |||
| 24 | Three things are deliberate: | ||
| 25 | |||
| 26 | - **One first-boot file per role.** The two templates differ in what they install | ||
| 27 | and which account they create, and nothing else. Neither has a copy of the | ||
| 28 | other's setup steps. | ||
| 29 | - **No machine images.** A golden image per cloud would need a publishing pipeline | ||
| 30 | and a per-cloud import, and it would go stale between releases. Installing a | ||
| 31 | `.deb` during first boot takes about thirty seconds and always installs the | ||
| 32 | version you named. | ||
| 33 | - **Nothing here runs twice.** First boot happens once; changing the instance | ||
| 34 | afterwards is editing `/etc/vole/vole.toml` and restarting, and upgrading is a | ||
| 35 | package install. See [Packages](../docs/packaging.md). | ||
| 36 | |||
| 17 | 37 | The runner's unit and its configuration example ship in the package, not here: | |
| 18 | 38 | `packaging/systemd/vole-runner.service` and `packaging/runner.env`. One binary is | |
| 19 | 39 | the forge, the runner, the CLI, and the library, so one package carries both | |
| 20 | 40 | units. | |
| 21 | 41 | ||
| 22 | Two things are deliberate: | ||
| 23 | |||
| 24 | - **The cloud-init file is the single source of truth.** The Terraform | ||
| 25 | configurations render it with `templatefile()`; `render.sh` renders it with | ||
| 26 | `envsubst`. Neither has its own copy of the setup steps, so a fix lands | ||
| 27 | everywhere at once. | ||
| 28 | - **There are no machine images.** A golden image per cloud would need a | ||
| 29 | publishing pipeline and a per-cloud import, and it would go stale between | ||
| 30 | releases. Installing a `.deb` during first boot takes about thirty seconds and | ||
| 31 | always installs the version you named. | ||
| 32 | |||
| 33 | Nothing here configures a machine after that first boot. Upgrades are a package | ||
| 34 | install on the machine, because a plan that replaces a server holding a team's | ||
| 35 | repositories is not a safe default. See [Packages](../docs/packaging.md). | ||
| 36 | |||
| 37 | 42 | `scripts/check-deploy.py` and the `deployment files` step in | |
| 38 | 43 | `.vole/workflows/verify.yaml` check these files on every push: the YAML parses, | |
| 39 | the template renders with no placeholder left behind, the boot script is valid | ||
| 40 | `sh`, and the Terraform is formatted. | ||
| 44 | each template renders with no placeholder left behind, the boot scripts are valid | ||
| 45 | `sh`, the container files name paths that exist, and the runner's template, unit, | ||
| 46 | and packaged example agree. It needs no cloud credentials and no network. | ||