Work for #200. Deploying Vole to a server is now one command per cloud, one rendered file for everything else, and one container for host-based setups.
What a user does
On Hetzner Cloud, DigitalOcean, or AWS:
cd deploy/terraform/hetzner
terraform init
terraform apply -var domain=forge.example.com \
-var "admin_ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)" \
-var vole_package_url=<the .deb for this architecture>
Three variables, no file to edit, and about three minutes later
https://forge.example.com is up with one instance admin whose key is the one you
passed. Firewall, elastic/floating address, and the DNS record (when the zone is
in the same account) come with it.
Everything else — Vultr, Linode, Scaleway, a spare machine — renders the same
first-boot file with deploy/cloud-init/render.sh and pastes it into the
provider’s user-data field. A container host uses deploy/docker/, where the
entrypoint runs vole admin init once because an image cannot install at run
time.
Why the pieces are shaped this way
One setup script, three clouds. deploy/cloud-init/vole.yaml is the only
place the steps live; Terraform renders it with templatefile() and
render.sh with envsubst. Neither has its own copy, so a fix lands everywhere.
The package installs before the configuration is written. The .deb’s
postinstall runs vole admin init against the shipped config, which creates the
database and the repository tree at /var/lib/vole/data. Writing the
operator’s config first would put a file under a path dpkg owns and risk it being
moved aside; writing it second is the documented way to configure an instance,
and init is idempotent so a second run is a no-op.
Two TLS arrangements, chosen by one variable. acme (default) lets Vole hold
its own certificate on 80/443 and installs the one-capability drop-in the hardened
unit needs; proxy serves plain HTTP on loopback for operators who already run a
TLS proxy. Certificate issuance is lazy, so a machine booted before DNS resolves
does not enter a restart loop — the first visit after DNS answers does the
issuance.
No machine images. A golden image per cloud needs a publishing pipeline and a
per-cloud import, and goes stale between releases. The .deb install takes about
thirty seconds at first boot and installs exactly the version named. That is why
deploy/ has no Packer file.
apply does not upgrade. users.db migrates in place the first time a newer
binary opens it and cannot be rolled back, so the guides keep upgrades as a
package install with a backup first, and Terraform ignores user_data after
creation rather than replacing a machine that holds a team’s repositories.
Checks
scripts/check-deploy.py runs in the verify workflow:
- every YAML under
deploy/parses; - the template renders using the placeholder list
render.shuses, with nothing left behind and every promised file present; - the boot script and the container entrypoint are valid
shand executable; - the compose file’s build context, Dockerfile, and every
COPYsource exist.
Terraform is formatted with terraform fmt -check, from 1.17.1 pinned by checksum
for both amd64 and arm64 in the verify image, the way nfpm and govulncheck are
pinned in the steps.
Rehearsed, not assumed:
- built the
.deband ran the full first-boot script indebian:13-slim— package install, config install, account created as instance admin, and a second run that changes nothing; - started the server with both staged configurations: the proxy config answers
200 okon/healthz, and the ACME config listens on 2222/80/443 withautomatic HTTPS enabled; - built the container image, created an account in it, and served
/healthzfrom a named volume owned by the image’s user; terraform validatepasses in all three configurations;vole workflow run verifypasses, including the new step.
Open question, not blocking
The guides take the .deb URL from “a release page” and the examples use
example.com. The project’s release artifacts are attached to a release on its
own instance, and no stable public download URL is documented, so the guides
describe the shape (.../releases/<tag>/artifacts/vole_<version>-1_<arch>.deb)
and leave the host to the operator. Worth deciding before this is advertised as a
one-command deployment.