183 lines
5.3 KiB
Terraform
183 lines
5.3 KiB
Terraform
# dev: Gitea + Runner + Traefik (git.luke-else.co.uk, cicd.luke-else.co.uk)
|
|
|
|
# Port sources: services/dev/*docker-compose.yml (published ports) and services/todo.md
|
|
# (the documented UFW allow-list). dev has no firewall in docs/architecture.md, but we
|
|
# add one anyway for baseline safety - see conversation history.
|
|
resource "hcloud_firewall" "this" {
|
|
name = "dev-firewall"
|
|
|
|
rule { # server ssh - wired to the vpn server's public IP only, see infra/main.tf
|
|
direction = "in"
|
|
protocol = "tcp"
|
|
port = "22"
|
|
source_ips = var.allowed_ssh_source_ips
|
|
}
|
|
|
|
rule { # gitea ssh (git.luke-else.co.uk, published as 222:22)
|
|
direction = "in"
|
|
protocol = "tcp"
|
|
port = "222"
|
|
source_ips = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
rule { # Traefik http/https (git.luke-else.co.uk, cicd.luke-else.co.uk)
|
|
direction = "in"
|
|
protocol = "tcp"
|
|
port = "80"
|
|
source_ips = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
rule {
|
|
direction = "in"
|
|
protocol = "tcp"
|
|
port = "443"
|
|
source_ips = ["0.0.0.0/0", "::/0"]
|
|
}
|
|
|
|
rule { # traffic from prod over the private network
|
|
direction = "in"
|
|
protocol = "tcp"
|
|
port = "1-65535"
|
|
source_ips = [var.network_ip_range]
|
|
}
|
|
|
|
rule {
|
|
direction = "in"
|
|
protocol = "udp"
|
|
port = "1-65535"
|
|
source_ips = [var.network_ip_range]
|
|
}
|
|
}
|
|
|
|
resource "hcloud_server" "this" {
|
|
name = "dev"
|
|
server_type = var.server_type
|
|
image = var.image
|
|
location = var.location
|
|
ssh_keys = var.ssh_key_ids
|
|
firewall_ids = [hcloud_firewall.this.id]
|
|
|
|
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" {
|
|
name = "dev-storage"
|
|
size = var.volume_size
|
|
server_id = hcloud_server.this.id
|
|
automount = true
|
|
format = "ext4"
|
|
|
|
lifecycle {
|
|
prevent_destroy = true
|
|
}
|
|
}
|
|
|
|
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.
|
|
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,
|
|
]
|
|
}
|