feat: Added automatic script copying functionality
This commit is contained in:
@@ -61,6 +61,7 @@ module "dev" {
|
||||
network_ip_range = var.network_ip_range
|
||||
bootstrap_script = local.bootstrap_script
|
||||
ssh_private_key_path = var.ssh_private_key_path
|
||||
deploy_user = var.deploy_user
|
||||
runner_count = var.dev_runner_count
|
||||
|
||||
# module.network.id alone doesn't guarantee the subnet exists yet, and a server
|
||||
@@ -82,6 +83,7 @@ module "prod" {
|
||||
network_ip_range = var.network_ip_range
|
||||
bootstrap_script = local.bootstrap_script
|
||||
ssh_private_key_path = var.ssh_private_key_path
|
||||
deploy_user = var.deploy_user
|
||||
|
||||
depends_on = [module.network]
|
||||
}
|
||||
@@ -96,6 +98,7 @@ module "vpn" {
|
||||
allowed_ssh_source_ips = var.allowed_ssh_source_ips
|
||||
bootstrap_script = local.bootstrap_script
|
||||
ssh_private_key_path = var.ssh_private_key_path
|
||||
deploy_user = var.deploy_user
|
||||
}
|
||||
|
||||
module "dns" {
|
||||
|
||||
@@ -94,6 +94,14 @@ resource "hcloud_volume" "storage" {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -101,6 +109,74 @@ 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,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,3 +5,8 @@ output "ipv4" {
|
||||
output "private_ipv4" {
|
||||
value = var.private_ip
|
||||
}
|
||||
|
||||
output "data_dir" {
|
||||
description = "Deterministic host path of the auto-mounted volume - see local.data_dir."
|
||||
value = local.data_dir
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
# 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}:
|
||||
@@ -13,7 +18,7 @@ services:
|
||||
container_name: gitea_runner_${i + 1}
|
||||
volumes:
|
||||
- ./config.yaml:/config.yaml
|
||||
- ./gitea_runner_${i + 1}:/data
|
||||
- ${data_dir}/gitea_runner_${i + 1}:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
@@ -57,3 +57,8 @@ 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
|
||||
}
|
||||
|
||||
@@ -120,3 +120,75 @@ resource "hcloud_volume" "storage" {
|
||||
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}"
|
||||
}
|
||||
|
||||
# DATA_DIR is picked up automatically by `docker compose` (which auto-loads
|
||||
# .env from its working directory) for every prod/*-docker-compose.yml bind
|
||||
# mount - see services/prod/*.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/prod/.env"
|
||||
content = "DATA_DIR=${local.data_dir}\n"
|
||||
file_permission = "0644"
|
||||
}
|
||||
|
||||
locals {
|
||||
# Everything under services/prod except .env: that's tracked via its own
|
||||
# resource content (reading it back with fileset()/filesha1() before it
|
||||
# exists would fail during `tofu plan`).
|
||||
prod_static_files = sort([
|
||||
for f in fileset("${path.root}/../services/prod", "**") : f if f != ".env"
|
||||
])
|
||||
|
||||
prod_static_files_hash = sha1(join("", [
|
||||
for f in local.prod_static_files : filesha1("${path.root}/../services/prod/${f}")
|
||||
]))
|
||||
}
|
||||
|
||||
# Copies services/prod to the server on every apply that changes it (a
|
||||
# hand-edited compose file, ...) or recreates the server - not just once at
|
||||
# first creation. Split from hcloud_server.this because it must run after
|
||||
# local_file.env, which in turn depends 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.prod_static_files_hash
|
||||
env = local_file.env.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/prod"
|
||||
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/prod/*.sh",
|
||||
]
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
hcloud_server.this,
|
||||
local_file.env,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,3 +5,8 @@ output "ipv4" {
|
||||
output "private_ipv4" {
|
||||
value = var.private_ip
|
||||
}
|
||||
|
||||
output "data_dir" {
|
||||
description = "Deterministic host path of the auto-mounted volume - see local.data_dir."
|
||||
value = local.data_dir
|
||||
}
|
||||
|
||||
@@ -52,3 +52,8 @@ 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 "deploy_user" {
|
||||
description = "Non-root sudo user created by the bootstrap script - services/prod is copied into this user's home directory."
|
||||
type = string
|
||||
}
|
||||
|
||||
@@ -58,3 +58,47 @@ resource "hcloud_server" "this" {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
vpn_static_files = sort(fileset("${path.root}/../services/vpn", "**"))
|
||||
|
||||
vpn_static_files_hash = sha1(join("", [
|
||||
for f in local.vpn_static_files : filesha1("${path.root}/../services/vpn/${f}")
|
||||
]))
|
||||
}
|
||||
|
||||
# Copies services/vpn to the server on every apply that changes it (a
|
||||
# hand-edited compose file, ...) or recreates the server - not just once at
|
||||
# first creation. Split from hcloud_server.this so it's triggered by content
|
||||
# changes independently of the bootstrap provisioners above.
|
||||
resource "null_resource" "deploy_services" {
|
||||
triggers = {
|
||||
server_id = hcloud_server.this.id
|
||||
static_files = local.vpn_static_files_hash
|
||||
}
|
||||
|
||||
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/vpn"
|
||||
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/vpn/*.sh",
|
||||
]
|
||||
}
|
||||
|
||||
depends_on = [hcloud_server.this]
|
||||
}
|
||||
|
||||
@@ -32,3 +32,8 @@ 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 "deploy_user" {
|
||||
description = "Non-root sudo user created by the bootstrap script - services/vpn is copied into this user's home directory."
|
||||
type = string
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ output "prod_private_ipv4" {
|
||||
value = module.prod.private_ipv4
|
||||
}
|
||||
|
||||
output "dev_data_dir" {
|
||||
description = "Deterministic host path of dev's auto-mounted volume - also written to services/dev/.env as DATA_DIR."
|
||||
value = module.dev.data_dir
|
||||
}
|
||||
|
||||
output "prod_data_dir" {
|
||||
description = "Deterministic host path of prod's auto-mounted volume - also written to services/prod/.env as DATA_DIR."
|
||||
value = module.prod.data_dir
|
||||
}
|
||||
|
||||
output "vpn_ipv4" {
|
||||
value = module.vpn.ipv4
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ terraform {
|
||||
source = "hashicorp/local"
|
||||
version = "~> 2.5"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "~> 3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user