my-homelab-configs/bootstrap/edge/main.tf

286 lines
8.7 KiB
HCL

terraform {
required_version = ">= 1.0"
required_providers {
null = {
source = "hashicorp/null"
version = "~> 3.2"
}
}
}
locals {
compose_file = templatefile("${path.module}/templates/docker-compose.yml.tftpl", {})
default_conf = templatefile("${path.module}/templates/default.conf.tftpl", {
server_name = var.server_name
backend_host = var.backend_host
demos_backend_port = var.demos_backend_port
})
default_vcl = templatefile("${path.module}/templates/default.vcl.tftpl", {
backend_host = var.backend_host
backend_port = tostring(var.backend_port)
})
haproxy_cfg = templatefile("${path.module}/templates/haproxy.cfg.tftpl", {
stats_user = var.haproxy_stats_user
stats_password = var.haproxy_stats_password
})
squid_conf = templatefile("${path.module}/templates/squid.conf.tftpl", {
backend_host = var.backend_host
backend_port = tostring(var.backend_port)
})
config_hash = sha256(join("\n---\n", [
local.compose_file,
local.default_conf,
local.default_vcl,
local.haproxy_cfg,
local.squid_conf,
]))
}
resource "null_resource" "edge_services" {
triggers = {
host = var.edge_host
user = var.edge_user
install_dir = var.edge_install_dir
server_name = var.server_name
enable_letsencrypt = tostring(var.enable_letsencrypt)
letsencrypt_email = var.letsencrypt_email
letsencrypt_staging = tostring(var.letsencrypt_staging)
certbot_version = "2"
config_hash = local.config_hash
}
connection {
type = "ssh"
user = self.triggers.user
private_key = file(var.edge_ssh_key_path)
host = self.triggers.host
}
provisioner "remote-exec" {
inline = [
"rm -rf /tmp/homelab-edge",
"mkdir -p /tmp/homelab-edge/config_files",
]
}
provisioner "file" {
content = local.compose_file
destination = "/tmp/homelab-edge/docker-compose.yml"
}
provisioner "file" {
content = local.default_conf
destination = "/tmp/homelab-edge/config_files/default.conf"
}
provisioner "file" {
content = local.default_vcl
destination = "/tmp/homelab-edge/config_files/default.vcl"
}
provisioner "file" {
content = local.haproxy_cfg
destination = "/tmp/homelab-edge/config_files/haproxy.cfg"
}
provisioner "file" {
content = local.squid_conf
destination = "/tmp/homelab-edge/config_files/squid.conf"
}
provisioner "remote-exec" {
inline = [
<<EOT
set -eu
install_dir="${self.triggers.install_dir}"
server_name="${self.triggers.server_name}"
enable_letsencrypt="${self.triggers.enable_letsencrypt}"
letsencrypt_email="${self.triggers.letsencrypt_email}"
letsencrypt_staging="${self.triggers.letsencrypt_staging}"
certbot_image="certbot/certbot:latest"
install_missing_packages() {
missing_packages=""
for package in "$@"; do
if ! dpkg-query -W -f='$${Status}' "$package" 2>/dev/null | grep -q "install ok installed"; then
missing_packages="$missing_packages $package"
fi
done
if [ -n "$missing_packages" ]; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends $missing_packages
fi
}
install_missing_packages ca-certificates curl openssl
if ! command -v docker >/dev/null 2>&1; then
curl -fsSL https://get.docker.com | sudo sh
fi
if ! sudo docker compose version >/dev/null 2>&1; then
install_missing_packages docker-compose-plugin
fi
sudo mkdir -p \
"$install_dir/config_files" \
"$install_dir/certs" \
"$install_dir/certbot/www" \
"$install_dir/letsencrypt"
sudo cp /tmp/homelab-edge/docker-compose.yml "$install_dir/docker-compose.yml"
sudo cp /tmp/homelab-edge/config_files/default.conf "$install_dir/config_files/default.conf"
sudo cp /tmp/homelab-edge/config_files/default.vcl "$install_dir/config_files/default.vcl"
sudo cp /tmp/homelab-edge/config_files/haproxy.cfg "$install_dir/config_files/haproxy.cfg"
sudo cp /tmp/homelab-edge/config_files/squid.conf "$install_dir/config_files/squid.conf"
if [ ! -s "$install_dir/certs/current.crt" ] || [ ! -s "$install_dir/certs/current.key" ]; then
sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-subj "/CN=$server_name" \
-keyout "$install_dir/certs/current.key" \
-out "$install_dir/certs/current.crt"
fi
deploy_current_certificate() {
if ! sudo test -s "$install_dir/letsencrypt/live/$server_name/fullchain.pem" ||
! sudo test -s "$install_dir/letsencrypt/live/$server_name/privkey.pem"; then
return 1
fi
sudo cp -L "$install_dir/letsencrypt/live/$server_name/fullchain.pem" "$install_dir/certs/current.crt"
sudo cp -L "$install_dir/letsencrypt/live/$server_name/privkey.pem" "$install_dir/certs/current.key"
sudo chmod 0644 "$install_dir/certs/current.crt"
sudo chmod 0600 "$install_dir/certs/current.key"
}
install_renewal_timer() {
sudo tee /usr/local/sbin/homelab-edge-renew-certs.sh >/dev/null <<RENEW_EOT
#!/bin/sh
set -eu
docker run --rm -v "$install_dir/letsencrypt:/etc/letsencrypt" -v "$install_dir/certbot/www:/var/www/certbot" "$certbot_image" renew --webroot -w /var/www/certbot --quiet
if [ -s "$install_dir/letsencrypt/live/$server_name/fullchain.pem" ] &&
[ -s "$install_dir/letsencrypt/live/$server_name/privkey.pem" ]; then
cp -L "$install_dir/letsencrypt/live/$server_name/fullchain.pem" "$install_dir/certs/current.crt"
cp -L "$install_dir/letsencrypt/live/$server_name/privkey.pem" "$install_dir/certs/current.key"
chmod 0644 "$install_dir/certs/current.crt"
chmod 0600 "$install_dir/certs/current.key"
cd "$install_dir"
docker compose exec -T nginx-dev nginx -s reload || docker compose restart nginx-dev
fi
RENEW_EOT
sudo chmod 0755 /usr/local/sbin/homelab-edge-renew-certs.sh
sudo tee /etc/systemd/system/homelab-edge-renew-certs.service >/dev/null <<'SERVICE_EOT'
[Unit]
Description=Renew Homelab Edge TLS certificates
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/homelab-edge-renew-certs.sh
SERVICE_EOT
sudo tee /etc/systemd/system/homelab-edge-renew-certs.timer >/dev/null <<'TIMER_EOT'
[Unit]
Description=Renew Homelab Edge TLS certificates twice daily
[Timer]
OnCalendar=*-*-* 03,15:17:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
TIMER_EOT
sudo systemctl daemon-reload
sudo systemctl enable --now homelab-edge-renew-certs.timer >/dev/null
}
check_edge_health() {
if ! curl "$@" >/dev/null; then
sudo docker compose ps || true
sudo docker compose logs --tail=80 nginx-dev || true
exit 1
fi
}
cleanup_legacy_edge_containers() {
local container
for container in nginx-dev haproxy-dev varnish-dev squid-dev; do
if sudo docker container inspect "$container" >/dev/null 2>&1; then
sudo docker rm -f "$container" >/dev/null
fi
done
}
cd "$install_dir"
cleanup_legacy_edge_containers
sudo docker compose pull
sudo docker compose up -d --remove-orphans
sudo docker compose ps
if [ "$enable_letsencrypt" = "true" ]; then
email_args="--register-unsafely-without-email"
if [ -n "$letsencrypt_email" ]; then
email_args="--email $letsencrypt_email"
fi
staging_args=""
if [ "$letsencrypt_staging" = "true" ]; then
staging_args="--staging"
fi
certbot_log="$(mktemp)"
if ! sudo docker run --rm \
-v "$install_dir/letsencrypt:/etc/letsencrypt" \
-v "$install_dir/certbot/www:/var/www/certbot" \
"$certbot_image" certonly \
--webroot \
-w /var/www/certbot \
-d "$server_name" \
--preferred-challenges http \
--agree-tos \
--non-interactive \
--keep-until-expiring \
$email_args \
$staging_args > "$certbot_log" 2>&1; then
cat "$certbot_log"
if grep -Eq "Certificate not yet due for renewal|no action taken" "$certbot_log" &&
sudo test -s "$install_dir/letsencrypt/live/$server_name/fullchain.pem" &&
sudo test -s "$install_dir/letsencrypt/live/$server_name/privkey.pem"; then
echo "Using existing Let's Encrypt certificate for $server_name"
else
rm -f "$certbot_log"
exit 1
fi
else
cat "$certbot_log"
fi
rm -f "$certbot_log"
deploy_current_certificate
sudo docker compose exec -T nginx-dev nginx -s reload || sudo docker compose restart nginx-dev
install_renewal_timer
check_edge_health -fsS --connect-timeout 10 --resolve "$server_name:443:127.0.0.1" "https://$server_name/edge-health"
else
sudo systemctl disable --now homelab-edge-renew-certs.timer >/dev/null 2>&1 || true
check_edge_health -kfsS --connect-timeout 10 https://127.0.0.1/edge-health
fi
EOT
]
}
}
output "edge_host" {
value = var.edge_host
}
output "edge_install_dir" {
value = var.edge_install_dir
}