feat: Swapped tofu init scripting for ansible scripting

This commit is contained in:
2026-07-13 21:23:31 +01:00
parent cf5504281c
commit c12a507fc1
34 changed files with 471 additions and 501 deletions
+4 -99
View File
@@ -56,30 +56,12 @@ resource "hcloud_server" "this" {
location = var.location
ssh_keys = var.ssh_key_ids
firewall_ids = [hcloud_firewall.this.id]
labels = { role = "dev" } # picked up by ansible/inventory/hcloud.yml
network {
network_id = var.network_id
ip = var.private_ip
}
connection {
type = "ssh"
host = self.ipv4_address
user = "root"
private_key = file(var.ssh_private_key_path)
}
provisioner "file" {
content = var.bootstrap_script
destination = "/root/bootstrap.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /root/bootstrap.sh",
"/root/bootstrap.sh",
]
}
}
resource "hcloud_volume" "storage" {
@@ -98,85 +80,8 @@ locals {
# Hetzner's automount convention (hc-utils' 99-hc-volume-automount.rules) is
# always /mnt/HC_Volume_<id> - deterministic once the volume exists, since the
# id is stable for the volume's lifetime regardless of which server it's
# attached to.
# attached to. Kept here only as a human-readable output (see outputs.tf) -
# ansible/roles/deploy looks this up itself from the Hetzner API, it doesn't
# read this value.
data_dir = "/mnt/HC_Volume_${hcloud_volume.storage.id}"
}
# Renders services/dev/Runners/docker-compose.yml directly into the repo, with
# one runner service per var.runner_count. This only touches the local working
# tree - deploying the change still goes through the normal scp + spinup.sh flow.
resource "local_file" "runners_compose" {
filename = "${path.root}/../services/dev/Runners/docker-compose.yml"
content = templatefile("${path.module}/templates/runners-docker-compose.yml.tftpl", {
runner_count = var.runner_count
data_dir = local.data_dir
})
file_permission = "0644"
}
# DATA_DIR is picked up automatically by `docker compose` (which auto-loads
# .env from its working directory) for every dev/*-docker-compose.yml bind
# mount - see services/dev/*.yml. Not committed (see .gitignore): it's tied to
# the live volume's ID, so it's regenerated by tofu apply, not handed off via git.
resource "local_file" "env" {
filename = "${path.root}/../services/dev/.env"
content = "DATA_DIR=${local.data_dir}\n"
file_permission = "0644"
}
locals {
# Everything under services/dev except the two files above: those are
# tracked via their own resource content (reading them back with fileset()/
# filesha1() before they exist would fail during `tofu plan`).
dev_static_files = sort([
for f in fileset("${path.root}/../services/dev", "**") :
f if f != ".env" && f != "Runners/docker-compose.yml"
])
dev_static_files_hash = sha1(join("", [
for f in local.dev_static_files : filesha1("${path.root}/../services/dev/${f}")
]))
}
# Copies services/dev to the server on every apply that changes it (a hand-edited
# compose file, a new runner count, ...) or recreates the server - not just once
# at first creation. Split from hcloud_server.this because it must run after the
# generated files above, which in turn depend on the volume, which depends on
# the server - so it can't be a provisioner on the server resource itself.
resource "null_resource" "deploy_services" {
triggers = {
server_id = hcloud_server.this.id
static_files = local.dev_static_files_hash
env = local_file.env.content
runners = local_file.runners_compose.content
}
connection {
type = "ssh"
host = hcloud_server.this.ipv4_address
user = "root"
private_key = file(var.ssh_private_key_path)
}
provisioner "remote-exec" {
inline = ["mkdir -p /home/${var.deploy_user}/services"]
}
provisioner "file" {
source = "${path.root}/../services/dev"
destination = "/home/${var.deploy_user}/services"
}
provisioner "remote-exec" {
inline = [
"chown -R ${var.deploy_user}:${var.deploy_user} /home/${var.deploy_user}/services",
"chmod +x /home/${var.deploy_user}/services/dev/*.sh",
]
}
depends_on = [
hcloud_server.this,
local_file.env,
local_file.runners_compose,
]
}
@@ -1,35 +0,0 @@
# Generated by OpenTofu from var.dev_runner_count - do not hand-edit.
# To change the number of runners, edit dev_runner_count in terraform.tfvars and
# run tofu apply. To change their shape, edit
# infra/modules/dev/templates/runners-docker-compose.yml.tftpl instead.
#
# GITEA_RUNNER_REGISTRATION_TOKEN is intentionally left as a compose variable
# (not baked in here) - services/dev/spinup.sh generates a fresh token and
# writes it to Runners/.env immediately before starting these containers.
#
# /data lives on dev's volume (${data_dir}) so runner identity survives a
# server rebuild - baked in directly rather than via .env, since Runners/.env
# is already reserved for the registration token above and gets overwritten
# on every deploy.
services:
%{ for i in range(runner_count) ~}
runner-${i + 1}:
image: gitea/act_runner:latest
container_name: gitea_runner_${i + 1}
volumes:
- ./config.yaml:/config.yaml
- ${data_dir}/gitea_runner_${i + 1}:/data
- /var/run/docker.sock:/var/run/docker.sock
networks:
- proxy
environment:
CONFIG_FILE: /config.yaml
GITEA_INSTANCE_URL: "https://git.luke-else.co.uk"
GITEA_RUNNER_REGISTRATION_TOKEN: "$${GITEA_RUNNER_REGISTRATION_TOKEN}"
GITEA_RUNNER_NAME: "CICD#${i + 1}"
restart: unless-stopped
%{ endfor ~}
networks:
proxy:
external: true
-20
View File
@@ -42,23 +42,3 @@ variable "network_ip_range" {
description = "CIDR of the private network, allowed through the firewall for traffic from prod."
type = string
}
variable "bootstrap_script" {
description = "Rendered post-install script, uploaded and executed on the server immediately after creation."
type = string
}
variable "ssh_private_key_path" {
description = "Local path to the private key matching one of var.ssh_key_ids, used to run the bootstrap script over SSH."
type = string
}
variable "runner_count" {
description = "Number of Gitea Actions runner containers to render into services/dev/Runners/docker-compose.yml."
type = number
}
variable "deploy_user" {
description = "Non-root sudo user created by the bootstrap script - services/dev is copied into this user's home directory."
type = string
}