From 1b9eeb12887159808aba1d2b173ccbc6f687f8c5 Mon Sep 17 00:00:00 2001 From: Florian Maury Date: Fri, 10 Jan 2025 16:10:39 +0100 Subject: [PATCH] blip --- main.tf | 33 ++ modules/acme_server/main.tf | 60 +++ modules/acme_server/outputs.tf | 3 + modules/acme_server/variables.tf | 5 + modules/caddy_reverse/files/Caddyfile.tftpl | 25 + .../caddy_reverse/files/caddy.container.tftpl | 20 + .../caddy_reverse/files/caddy.network.tftpl | 5 + .../files/caddy_data.volume.tftpl | 11 + modules/caddy_reverse/main.tf | 151 ++++++ modules/caddy_reverse/outputs.tf | 3 + modules/caddy_reverse/variables.tf | 16 + modules/castopod/files/Caddyfile.tftpl | 20 + .../files/caddy-frontend.network.tftpl | 6 + modules/castopod/files/caddy.container.tftpl | 21 + .../files/castopod-backend.network.tftpl | 6 + .../files/castopod-frontend.network.tftpl | 5 + .../files/castopod-media.volume.tftpl | 8 + .../castopod/files/castopod.container.tftpl | 30 ++ .../files/generate_secrets.service.tftpl | 20 + .../castopod/files/generate_secrets.sh.tftpl | 22 + .../castopod/files/mariadb-data.volume.tftpl | 8 + .../castopod/files/mariadb.container.tftpl | 28 ++ modules/castopod/files/secrets.mount.tftpl | 11 + modules/castopod/files/valkey.container.tftpl | 25 + modules/castopod/files/valkey.volume.tftpl | 8 + modules/castopod/main.tf | 452 ++++++++++++++++++ modules/castopod/outputs.tf | 3 + modules/castopod/variables.tf | 19 + modules/dns_resolver/.gitignore | 2 + .../dns_resolver/files/dhcp_config.conf.tftpl | 3 + modules/dns_resolver/main.tf | 168 +++++++ modules/dns_resolver/outputs.tf | 0 modules/dns_resolver/variables.tf | 70 +++ outputs.tf | 7 + settings.tfvars => settings.auto.tfvars | 0 35 files changed, 1274 insertions(+) create mode 100644 modules/acme_server/main.tf create mode 100644 modules/acme_server/outputs.tf create mode 100644 modules/acme_server/variables.tf create mode 100644 modules/caddy_reverse/files/Caddyfile.tftpl create mode 100644 modules/caddy_reverse/files/caddy.container.tftpl create mode 100644 modules/caddy_reverse/files/caddy.network.tftpl create mode 100644 modules/caddy_reverse/files/caddy_data.volume.tftpl create mode 100644 modules/caddy_reverse/main.tf create mode 100644 modules/caddy_reverse/outputs.tf create mode 100644 modules/caddy_reverse/variables.tf create mode 100644 modules/castopod/files/Caddyfile.tftpl create mode 100644 modules/castopod/files/caddy-frontend.network.tftpl create mode 100644 modules/castopod/files/caddy.container.tftpl create mode 100644 modules/castopod/files/castopod-backend.network.tftpl create mode 100644 modules/castopod/files/castopod-frontend.network.tftpl create mode 100644 modules/castopod/files/castopod-media.volume.tftpl create mode 100644 modules/castopod/files/castopod.container.tftpl create mode 100644 modules/castopod/files/generate_secrets.service.tftpl create mode 100644 modules/castopod/files/generate_secrets.sh.tftpl create mode 100644 modules/castopod/files/mariadb-data.volume.tftpl create mode 100644 modules/castopod/files/mariadb.container.tftpl create mode 100644 modules/castopod/files/secrets.mount.tftpl create mode 100644 modules/castopod/files/valkey.container.tftpl create mode 100644 modules/castopod/files/valkey.volume.tftpl create mode 100644 modules/castopod/main.tf create mode 100644 modules/castopod/outputs.tf create mode 100644 modules/castopod/variables.tf create mode 100644 modules/dns_resolver/.gitignore create mode 100644 modules/dns_resolver/files/dhcp_config.conf.tftpl create mode 100644 modules/dns_resolver/main.tf create mode 100644 modules/dns_resolver/outputs.tf create mode 100644 modules/dns_resolver/variables.tf rename settings.tfvars => settings.auto.tfvars (100%) diff --git a/main.tf b/main.tf index 5272344..3b2ae9a 100644 --- a/main.tf +++ b/main.tf @@ -57,3 +57,36 @@ module "poc" { } admin_ssh_public_key = var.ssh_public_key_admin_netboot_server } + +locals { + castopod_domain = "pod.broken-by-design.fr" + castopod_upstream_port = 8000 +} + +module "castopod_config" { + source = "./modules/castopod" + + base_url = "https://pod.broken-by-design.fr/" + castopod_domain = local.castopod_domain + castopod_upstream_port = local.castopod_upstream_port + ssh_authorized_keys = [ + file("/var/home/fmaury/.ssh/fma_ovh_rise2.pub") + ] +} + +module "caddy_config" { + source = "./modules/caddy_reverse" + + vhosts = [ + { + domain = local.castopod_domain + upstreams = [ + "10.109.0.13:${local.castopod_upstream_port}" + ] + } + ] + + ssh_authorized_keys = [ + file("/var/home/fmaury/.ssh/fma_ovh_rise2.pub") + ] +} diff --git a/modules/acme_server/main.tf b/modules/acme_server/main.tf new file mode 100644 index 0000000..82c5301 --- /dev/null +++ b/modules/acme_server/main.tf @@ -0,0 +1,60 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "~>0.56.1" + } + ignition = { + source = "community-terraform-providers/ignition" + version = "2.3.4" + } + } + required_version = ">=1.6.2" +} + +data "ignition_disk" "data" { + device = "/dev/disk/by-path/0000:00:0b.0" + + partition { + label = "caddy_config" + number = 0 + sizemib = 100 + startmib = 0 + type_guid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" + } + partition { + label = "caddy_data" + number = 0 + sizemib = 1000 + startmib = 0 + type_guid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" + } +} + +data "ignition_filesystem" "caddy_config" { + device = "/dev/disk/by-label/caddy_config" + format = "btrfs" + wipe_filesystem = true + label = "caddy_config" + path = "/caddy/config" + mount_options = ["nodev", "noexec", "nosuid"] +} + +data "ignition_filesystem" "caddy_data" { + device = "/dev/disk/by-label/caddy_data" + format = "btrfs" + wipe_filesystem = true + label = "caddy_data" + path = "/caddy/data" + mount_options = ["nodev", "noexec", "nosuid"] +} + +data "ignition_config" "acme_server" { + disks = [ + data.ignition_disk.data.rendered, + ] + filesystems = [ + data.ignition_filesystem.caddy_config.rendered, + data.ignition_filesystem.caddy_data.rendered, + ] +} diff --git a/modules/acme_server/outputs.tf b/modules/acme_server/outputs.tf new file mode 100644 index 0000000..71fa7de --- /dev/null +++ b/modules/acme_server/outputs.tf @@ -0,0 +1,3 @@ +output "test" { + value = data.ignition_config.acme_server.rendered +} diff --git a/modules/acme_server/variables.tf b/modules/acme_server/variables.tf new file mode 100644 index 0000000..fecc4f5 --- /dev/null +++ b/modules/acme_server/variables.tf @@ -0,0 +1,5 @@ +variable "fcos_base_vm_id" { + type = number + nullable = false +} + diff --git a/modules/caddy_reverse/files/Caddyfile.tftpl b/modules/caddy_reverse/files/Caddyfile.tftpl new file mode 100644 index 0000000..bf2c684 --- /dev/null +++ b/modules/caddy_reverse/files/Caddyfile.tftpl @@ -0,0 +1,25 @@ +{ + skip_install_trust + # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory + log { + output stdout + format json + } +} + +%{ for vhost in vhosts ~} +${vhost.domain} { + reverse_proxy { +%{ for upstream in vhost.upstreams ~} + to https://${upstream} + transport http { + tls_server_name ${vhost.domain} + tls_insecure_skip_verify + } +%{ endfor ~} +%{ for hdr in vhost.headers_down ~} + header_down ${hdr.modifier}${hdr.name} "${hdr.value}" +%{ endfor ~} + } +} +%{ endfor ~} diff --git a/modules/caddy_reverse/files/caddy.container.tftpl b/modules/caddy_reverse/files/caddy.container.tftpl new file mode 100644 index 0000000..dfdeb6c --- /dev/null +++ b/modules/caddy_reverse/files/caddy.container.tftpl @@ -0,0 +1,20 @@ +[Unit] +Description = "Caddy Container" + +[Container] +ContainerName = caddy +Image = docker.io/caddy:${caddy_version} + +Volume = ${caddy_data_volume_name}.volume:/data:z +Volume = ${caddy_config_file_path}:/etc/caddy/Caddyfile:ro,z + +Network = ${caddy_network_name}.network + +PublishPort = 80:80 +PublishPort = 443:443 + +[Service] +Restart=on-failure + +[Install] +WantedBy=default.target diff --git a/modules/caddy_reverse/files/caddy.network.tftpl b/modules/caddy_reverse/files/caddy.network.tftpl new file mode 100644 index 0000000..b934353 --- /dev/null +++ b/modules/caddy_reverse/files/caddy.network.tftpl @@ -0,0 +1,5 @@ +[Unit] +Description = Caddy Network + +[Network] +NetworkName = "${caddy_network_name}" diff --git a/modules/caddy_reverse/files/caddy_data.volume.tftpl b/modules/caddy_reverse/files/caddy_data.volume.tftpl new file mode 100644 index 0000000..85ab0ac --- /dev/null +++ b/modules/caddy_reverse/files/caddy_data.volume.tftpl @@ -0,0 +1,11 @@ +[Unit] +Description = Caddy Data Volume + +[Volume] +VolumeName = ${caddy_data_volume_name} +Device=/dev/disk/by-label/${caddy_data_volume_name} +Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0 +Type=ext4 + +[Install] +WantedBy=default.target \ No newline at end of file diff --git a/modules/caddy_reverse/main.tf b/modules/caddy_reverse/main.tf new file mode 100644 index 0000000..1d85945 --- /dev/null +++ b/modules/caddy_reverse/main.tf @@ -0,0 +1,151 @@ +locals { + data_device_path = "/dev/vdb" + + caddy_version = "2.8.4-alpine" + + caddy_config_dir_path = "/opt/caddy_config" + caddy_data_volume_name = "caddy_data" + caddy_network_name = "caddy_net" + + data_disk = { + device = local.data_device_path + wipeTable = true + partitions = [ + { + label = local.caddy_data_volume_name + number = 1 + sizeMiB = 512 + wipePartitionEntry = true + shouldExist = true + resize = true + }, + ] + } + + caddy_data_filesystem = { + device = "${local.data_device_path}1" + format = "ext4" + label = local.caddy_data_volume_name + } + + caddy_data_volume_file = { + path = "/etc/containers/systemd/${local.caddy_data_volume_name}.volume" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/caddy_data.volume.tftpl", + { + caddy_data_volume_name = local.caddy_data_volume_name + } + ) + ) + ) + } + } + + caddy_config_directory = { + path = local.caddy_config_dir_path + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + } + + caddyfile_file = { + path = "${local.caddy_config_dir_path}/Caddyfile" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/Caddyfile.tftpl", + { + vhosts = var.vhosts + } + ) + ) + ) + } + } + + caddy_network_file = { + path = "/etc/containers/systemd/${local.caddy_network_name}.network" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/caddy.network.tftpl", + { + caddy_network_name = local.caddy_network_name + } + ) + ) + ) + } + } + + caddy_container_file = { + path = "/etc/containers/systemd/caddy.container" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/caddy.container.tftpl", + { + caddy_version = local.caddy_version + caddy_data_volume_name = local.caddy_data_volume_name + caddy_config_file_path = "${local.caddy_config_dir_path}/Caddyfile" + caddy_network_name = local.caddy_network_name + } + ) + ) + ) + } + } + + ignition_config = jsonencode({ + ignition = { + version = "3.4.0" + } + storage = { + disks = [ + local.data_disk, + ] + filesystems = [ + local.caddy_data_filesystem, + ] + files = [ + local.caddy_data_volume_file, + local.caddyfile_file, + local.caddy_network_file, + local.caddy_container_file, + ] + directories = [ + local.caddy_config_directory, + ] + } + passwd = { + users = [ + { + name = "core" + sshAuthorizedKeys = var.ssh_authorized_keys + } + ] + } + }) +} diff --git a/modules/caddy_reverse/outputs.tf b/modules/caddy_reverse/outputs.tf new file mode 100644 index 0000000..3f0fbc2 --- /dev/null +++ b/modules/caddy_reverse/outputs.tf @@ -0,0 +1,3 @@ +output "config" { + value = local.ignition_config +} diff --git a/modules/caddy_reverse/variables.tf b/modules/caddy_reverse/variables.tf new file mode 100644 index 0000000..3eb1a2a --- /dev/null +++ b/modules/caddy_reverse/variables.tf @@ -0,0 +1,16 @@ +variable "vhosts" { + type = list(object({ + domain = string + upstreams = list(string) + headers_down = optional(list(object({ + modifier = optional(string, "") + name = string + value = string + })), []) + })) +} + +variable "ssh_authorized_keys" { + type = list(string) + nullable = false +} diff --git a/modules/castopod/files/Caddyfile.tftpl b/modules/castopod/files/Caddyfile.tftpl new file mode 100644 index 0000000..b79f395 --- /dev/null +++ b/modules/castopod/files/Caddyfile.tftpl @@ -0,0 +1,20 @@ +{ + skip_install_trust + # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory + log { + output stdout + format json + } +} + +${castopod_domain}:8000 { + handle_path /media/* { + file_server { + root /media + } + } + reverse_proxy { + to ${castopod_container_name}:8000 + } + tls internal +} diff --git a/modules/castopod/files/caddy-frontend.network.tftpl b/modules/castopod/files/caddy-frontend.network.tftpl new file mode 100644 index 0000000..f960455 --- /dev/null +++ b/modules/castopod/files/caddy-frontend.network.tftpl @@ -0,0 +1,6 @@ +[Unit] +Description = Caddy Frontend Network + +[Network] +NetworkName = "${caddy_frontend_network_name}" +Internal = true diff --git a/modules/castopod/files/caddy.container.tftpl b/modules/castopod/files/caddy.container.tftpl new file mode 100644 index 0000000..35cd446 --- /dev/null +++ b/modules/castopod/files/caddy.container.tftpl @@ -0,0 +1,21 @@ +[Unit] +Description = "Caddy Container" + +[Container] +ContainerName = "${caddy_container_name}" +Image = "docker.io/caddy/caddy:${caddy_version}" + +Volume = ${caddy_config_dir}/Caddyfile:/etc/caddy/Caddyfile:ro +Volume = ${castopod_media_volume_name}.volume:/media:z + +Network = ${caddy_frontend_network_name}.network +Network = ${castopod_frontend_network_name}.network + +PublishPort=${castopod_upstream_port}:8000 + +[Service] +Restart=on-failure +ExecStartPre=/usr/bin/chcon -t container_file_t ${caddy_config_dir}/Caddyfile + +[Install] +WantedBy=default.target diff --git a/modules/castopod/files/castopod-backend.network.tftpl b/modules/castopod/files/castopod-backend.network.tftpl new file mode 100644 index 0000000..6452a28 --- /dev/null +++ b/modules/castopod/files/castopod-backend.network.tftpl @@ -0,0 +1,6 @@ +[Unit] +Description = Castopod Backend Network + +[Network] +NetworkName = "${castopod_backend_network_name}" +Internal = true diff --git a/modules/castopod/files/castopod-frontend.network.tftpl b/modules/castopod/files/castopod-frontend.network.tftpl new file mode 100644 index 0000000..5ceb00d --- /dev/null +++ b/modules/castopod/files/castopod-frontend.network.tftpl @@ -0,0 +1,5 @@ +[Unit] +Description = Castopod Frontend Network + +[Network] +NetworkName = "${castopod_frontend_network_name}" diff --git a/modules/castopod/files/castopod-media.volume.tftpl b/modules/castopod/files/castopod-media.volume.tftpl new file mode 100644 index 0000000..0121492 --- /dev/null +++ b/modules/castopod/files/castopod-media.volume.tftpl @@ -0,0 +1,8 @@ +[Unit] +Description = "Castopod Media Volume" + +[Volume] +VolumeName = ${castopod_media_volume_name} +Device=/dev/disk/by-label/${castopod_media_volume_name} +Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0 +Type=ext4 diff --git a/modules/castopod/files/castopod.container.tftpl b/modules/castopod/files/castopod.container.tftpl new file mode 100644 index 0000000..6baeb50 --- /dev/null +++ b/modules/castopod/files/castopod.container.tftpl @@ -0,0 +1,30 @@ +[Unit] +Description = "Castopod Container" + +Wants=generate_secrets.service +After=generate_secrets.service + +[Container] +ContainerName = "${castopod_container_name}" +Image = "docker.io/castopod/castopod:${castopod_version}" + +Volume = ${castopod_media_volume_name}.volume:/var/www/castopod/public/media:z + +Network = ${castopod_frontend_network_name}.network +Network = ${castopod_backend_network_name}.network + +Environment=CP_DATABASE_HOSTNAME=${mariadb_container_name} +Environment=CP_DATABASE_NAME=${castopod_db_name} +Environment=CP_DATABASE_USERNAME=${castopod_db_user} +Environment=CP_BASEURL=${castopod_base_url} +Environment=CP_CACHE_HANDLER=redis +Environment=CP_REDIS_HOST=${valkey_container_name} +EnvironmentFile=${secrets_path}/castopod-mariadb-password.env +EnvironmentFile=${secrets_path}/castopod-valkey.env +EnvironmentFile=${secrets_path}/castopod-analytics.env + +[Service] +Restart=on-failure + +[Install] +WantedBy=default.target diff --git a/modules/castopod/files/generate_secrets.service.tftpl b/modules/castopod/files/generate_secrets.service.tftpl new file mode 100644 index 0000000..7e8c5cf --- /dev/null +++ b/modules/castopod/files/generate_secrets.service.tftpl @@ -0,0 +1,20 @@ +[Unit] +Description = "Generate Secrets" + +Wants=${secrets_path_escaped}.mount +After=${secrets_path_escaped}.mount + +ConditionPathExists=!${secrets_path}/mariadb-root-password.env +ConditionPathExists=!${secrets_path}/mariadb-password.env +ConditionPathExists=!${secrets_path}/castopod-mariadb-password.env +ConditionPathExists=!${secrets_path}/castopod-analytics.env +ConditionPathExists=!${secrets_path}/castopod-valkey.env +ConditionPathExists=!${secrets_path}/valkey-entrypoint.sh + +[Service] +Type=oneshot +RemainAfterExit=true +ExecStart=/bin/bash /var/opt/generate_secrets.sh + +[Install] +WantedBy=default.target diff --git a/modules/castopod/files/generate_secrets.sh.tftpl b/modules/castopod/files/generate_secrets.sh.tftpl new file mode 100644 index 0000000..fcb8a50 --- /dev/null +++ b/modules/castopod/files/generate_secrets.sh.tftpl @@ -0,0 +1,22 @@ +#!/bin/bash + +set -o errexit -o nounset -o pipefail + +umask 7177 + +openssl rand -hex 16 | tr -d '\n' > "${secrets_path}/mariadb-root-password.secret" +(echo -n 'MARIADB_ROOT_PASSWORD=' ; cat ${secrets_path}/mariadb-root-password.secret) > "${secrets_path}/mariadb-root-password.env" + +openssl rand -hex 16 | tr -d '\n' > "${secrets_path}/castopod-mariadb.secret" +(echo -n 'MARIADB_PASSWORD=' ; cat "${secrets_path}/castopod-mariadb.secret") > "${secrets_path}/mariadb-password.env" +(echo -n 'CP_DATABASE_PASSWORD=' ; cat "${secrets_path}/castopod-mariadb.secret") > "${secrets_path}/castopod-mariadb-password.env" +(echo -n 'CP_ANALYTICS_SALT=' ; openssl rand -base64 16) > "${secrets_path}/castopod-analytics.env" + +openssl rand -hex 16 | tr -d '\n' > "${secrets_path}/castopod-valkey.secret" +(echo -n 'CP_REDIS_PASSWORD=' ; cat "${secrets_path}/castopod-valkey.secret") > "${secrets_path}/castopod-valkey.env" + +echo "#!/bin/sh" > '${secrets_path}/valkey-entrypoint.sh' +(echo '/usr/local/bin/docker-entrypoint.sh valkey-server --requirepass "' ; cat "${secrets_path}/castopod-valkey.secret" ; echo '"') >> '${secrets_path}/valkey-entrypoint.sh' +chown 999 '${secrets_path}/valkey-entrypoint.sh' +chmod 700 '${secrets_path}/valkey-entrypoint.sh' +chcon -t container_file_t '${secrets_path}/valkey-entrypoint.sh' diff --git a/modules/castopod/files/mariadb-data.volume.tftpl b/modules/castopod/files/mariadb-data.volume.tftpl new file mode 100644 index 0000000..6289d4b --- /dev/null +++ b/modules/castopod/files/mariadb-data.volume.tftpl @@ -0,0 +1,8 @@ +[Unit] +Description = "MariaDB data Volume" + +[Volume] +VolumeName = ${mariadb_data_volume_name} +Device=/dev/disk/by-label/${mariadb_data_volume_name} +Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0 +Type=ext4 diff --git a/modules/castopod/files/mariadb.container.tftpl b/modules/castopod/files/mariadb.container.tftpl new file mode 100644 index 0000000..a5bc1fe --- /dev/null +++ b/modules/castopod/files/mariadb.container.tftpl @@ -0,0 +1,28 @@ +[Unit] +Description = "MariaDB Container" + +Wants=generate_secrets.service +After=generate_secrets.service + +[Container] +ContainerName = "${mariadb_container_name}" +Image = "docker.io/mariadb:${mariadb_version}" + +Volume = ${mariadb_data_volume_name}.volume:/var/lib/mysql:z + +Network = ${castopod_backend_network_name}.network + +EnvironmentFile=${secrets_path}/mariadb-root-password.env +EnvironmentFile=${secrets_path}/mariadb-password.env +Environment=MARIADB_DATABASE=${castopod_db_name} +Environment=MARIADB_USER=${castopod_db_user} + +# skips chown that would cause a EPERM on lost+found +User=mysql +Group=mysql + +[Service] +Restart=on-failure + +[Install] +WantedBy=default.target diff --git a/modules/castopod/files/secrets.mount.tftpl b/modules/castopod/files/secrets.mount.tftpl new file mode 100644 index 0000000..3b41543 --- /dev/null +++ b/modules/castopod/files/secrets.mount.tftpl @@ -0,0 +1,11 @@ +[Unit] +Description = Mountpoint for persistent secrets + +[Mount] +What=LABEL=${secrets_part_name} +Where=/var/opt/secrets +Type=ext4 +Options=nosuid,nodev + +[Install] +WantedBy=local-fs.target diff --git a/modules/castopod/files/valkey.container.tftpl b/modules/castopod/files/valkey.container.tftpl new file mode 100644 index 0000000..2cbb7e7 --- /dev/null +++ b/modules/castopod/files/valkey.container.tftpl @@ -0,0 +1,25 @@ +[Unit] +Description = "Valkey Container" + +Wants=generate_secrets.service +After=generate_secrets.service + +[Container] +ContainerName = "${valkey_container_name}" +Image = "docker.io/valkey/valkey:${valkey_version}" + +Volume = ${secrets_path}/valkey-entrypoint.sh:/usr/local/bin/valkey-entrypoint.sh:ro +Volume = ${valkey_cache_volume_name}.volume:/data:z + +Network = ${castopod_backend_network_name}.network +Entrypoint=/usr/local/bin/valkey-entrypoint.sh + +# skips find/chown in docker entrypoint which tries to chown lost+found and receive a perm denied +User=valkey +Group=valkey + +[Service] +Restart=on-failure + +[Install] +WantedBy=default.target diff --git a/modules/castopod/files/valkey.volume.tftpl b/modules/castopod/files/valkey.volume.tftpl new file mode 100644 index 0000000..c2100e9 --- /dev/null +++ b/modules/castopod/files/valkey.volume.tftpl @@ -0,0 +1,8 @@ +[Unit] +Description = "Valkey Cache Volume" + +[Volume] +VolumeName = ${valkey_cache_volume_name} +Device=/dev/disk/by-label/${valkey_cache_volume_name} +Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0 +Type=ext4 diff --git a/modules/castopod/main.tf b/modules/castopod/main.tf new file mode 100644 index 0000000..2790c17 --- /dev/null +++ b/modules/castopod/main.tf @@ -0,0 +1,452 @@ +locals { + caddy_frontend_network_name = "caddy-frontend" + caddy_container_name = "caddy" + caddy_version = "2.9.1-alpine" + caddy_config_dir = "/var/opt/caddy" + + castopod_frontend_network_name = "castopod-frontend" + castopod_backend_network_name = "castopod-backend" + castopod_media_volume_name = "castopod-media" + castopod_container_name = "castopod" + + castopod_db_name = "castopod" + castopod_db_user = "castopod" + castopod_base_url = var.base_url + + valkey_container_name = "valkey" + valkey_cache_volume_name = "castopod-cache" + + mariadb_container_name = "mariadb" + mariadb_data_volume_name = "castopod-db" + mariadb_version = "11.5" + + secrets_part_name = "secrets" + secrets_path = "/var/opt/secrets" + secrets_path_escaped = "var-opt-secrets" + + data_device_path = "/dev/vdb" + + data_disk = { + device = local.data_device_path + wipeTable = true + partitions = [ + { + label = local.secrets_part_name + number = 1 + sizeMiB = 1024 + wipePartitionEntry = true + shouldExist = true + resize = true + }, + { + label = local.castopod_media_volume_name + number = 2 + sizeMiB = 20 * 1024 + wipePartitionEntry = true + shouldExist = true + resize = true + }, + { + label = local.mariadb_data_volume_name + number = 3 + sizeMiB = 5 * 1024 + wipePartitionEntry = true + shouldExist = true + resize = true + }, + { + label = local.valkey_cache_volume_name + number = 4 + sizeMiB = 1024 + wipePartitionEntry = true + shouldExist = true + resize = true + }, + ] + } + + caddy_config_directory = { + path = local.caddy_config_dir + user = {id = 0} + group = {id = 0} + mode = 448 # 0700 + } + + caddy_config_file = { + path = "${local.caddy_config_dir}/Caddyfile" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/Caddyfile.tftpl", + { + castopod_domain = var.castopod_domain + castopod_container_name = local.castopod_container_name + } + ) + ) + ) + } + } + + caddy_frontend_network_file = { + path = "/etc/containers/systemd/caddy-frontend.network" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/caddy-frontend.network.tftpl", + { + caddy_frontend_network_name = local.caddy_frontend_network_name + } + ) + ) + ) + } + } + + caddy_container_file = { + path = "/etc/containers/systemd/caddy.container" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/caddy.container.tftpl", + { + caddy_container_name = local.caddy_container_name + caddy_version = local.caddy_version + caddy_config_dir = local.caddy_config_dir + caddy_frontend_network_name = local.caddy_frontend_network_name + castopod_frontend_network_name = local.castopod_frontend_network_name + castopod_upstream_port = var.castopod_upstream_port + castopod_media_volume_name = local.castopod_media_volume_name + } + ) + ) + ) + } + } + + castopod_secrets_filesystem = { + device = "${local.data_device_path}1" + format = "ext4" + label = local.secrets_part_name + } + + castopod_secrets_directory = { + path = local.secrets_path + user = {id = 0} + group = {id = 0} + mode = 448 # 0700 + } + + castopod_secrets_mount_unit = { + name = "${local.secrets_path_escaped}.mount" + enabled = true + contents = templatefile( + "${path.module}/files/secrets.mount.tftpl", + { + secrets_part_name = local.secrets_part_name + secrets_path = local.secrets_path + } + ) + } + + castopod_generate_secrets_script_file = { + path = "/var/opt/generate_secrets.sh" + user = {id = 0} + group = {id = 0} + mode = 448 # 0700 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/generate_secrets.sh.tftpl", + { + secrets_path = local.secrets_path + } + ) + ) + ) + } + } + + castopod_generate_secrets_service_unit = { + name = "generate_secrets.service" + enabled = true + contents = templatefile( + "${path.module}/files/generate_secrets.service.tftpl", + { + secrets_path = local.secrets_path + secrets_path_escaped = local.secrets_path_escaped + } + ) + } + + castopod_frontend_network_file = { + path = "/etc/containers/systemd/${local.castopod_frontend_network_name}.network" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/castopod-frontend.network.tftpl", + { + castopod_frontend_network_name = local.castopod_frontend_network_name + } + ) + ) + ) + } + } + + castopod_backend_network_file = { + path = "/etc/containers/systemd/${local.castopod_backend_network_name}.network" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/castopod-backend.network.tftpl", + { + castopod_backend_network_name = local.castopod_backend_network_name + } + ) + ) + ) + } + } + + castopod_media_volume_filesystem = { + device = "${local.data_device_path}2" + format = "ext4" + label = local.castopod_media_volume_name + options = [ + "-E", "root_owner=33:33", + ] + } + + castopod_media_volume_file = { + path = "/etc/containers/systemd/${local.castopod_media_volume_name}.volume" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/castopod-media.volume.tftpl", + { + castopod_media_volume_name = local.castopod_media_volume_name + } + ) + ) + ) + } + } + + mariadb_data_volume_filesystem = { + device = "${local.data_device_path}3" + format = "ext4" + label = local.mariadb_data_volume_name + options = [ + "-E", "root_owner=999:999", + ] + + } + + mariadb_data_volume_file = { + path = "/etc/containers/systemd/${local.mariadb_data_volume_name}.volume" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/mariadb-data.volume.tftpl", + { + mariadb_data_volume_name = local.mariadb_data_volume_name + } + ) + ) + ) + } + } + + mariadb_container_file = { + path = "/etc/containers/systemd/${local.mariadb_container_name}.container" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/mariadb.container.tftpl", + { + mariadb_container_name = local.mariadb_container_name + mariadb_version = local.mariadb_version + mariadb_data_volume_name = local.mariadb_data_volume_name + castopod_backend_network_name = local.castopod_backend_network_name + castopod_db_name = local.castopod_db_name + castopod_db_user = local.castopod_db_user + secrets_path = local.secrets_path + } + ) + ) + ) + } + } + + valkey_cache_volume_filesystem = { + device = "${local.data_device_path}4" + format = "ext4" + label = local.valkey_cache_volume_name + options = [ + "-E", "root_owner=999:999", + ] + } + + valkey_cache_volume_file = { + path = "/etc/containers/systemd/${local.valkey_cache_volume_name}.volume" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/valkey.volume.tftpl", + { + valkey_cache_volume_name = local.valkey_cache_volume_name + } + ) + ) + ) + } + } + + valkey_container_file = { + path = "/etc/containers/systemd/${local.valkey_container_name}.container" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile( + "${path.module}/files/valkey.container.tftpl", + { + valkey_container_name = local.valkey_container_name + valkey_version = "8.0-alpine" + valkey_cache_volume_name = local.valkey_cache_volume_name + castopod_backend_network_name = local.castopod_backend_network_name + secrets_path = local.secrets_path + } + ) + ) + ) + } + } + + castopod_container_file = { + path = "/etc/containers/systemd/${local.castopod_container_name}.container" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode( + templatefile("${path.module}/files/castopod.container.tftpl", { + castopod_version = "1.13.2", + castopod_container_name = local.castopod_container_name + castopod_frontend_network_name = local.castopod_frontend_network_name + castopod_backend_network_name = local.castopod_backend_network_name + castopod_media_volume_name = local.castopod_media_volume_name + castopod_db_name = local.castopod_db_name + castopod_db_user = local.castopod_db_user + castopod_base_url = var.base_url + mariadb_container_name = local.mariadb_container_name + valkey_container_name = local.valkey_container_name + secrets_path = local.secrets_path + }) + ) + ) + } + } + + ignition_config = jsonencode({ + ignition = { + version = "3.4.0" + } + storage = { + disks = [ + local.data_disk, + ] + filesystems = [ + local.castopod_secrets_filesystem, + local.castopod_media_volume_filesystem, + local.mariadb_data_volume_filesystem, + local.valkey_cache_volume_filesystem, + ] + files = [ + local.caddy_config_file, + local.caddy_frontend_network_file, + local.caddy_container_file, + local.castopod_generate_secrets_script_file, + local.castopod_frontend_network_file, + local.castopod_backend_network_file, + local.castopod_media_volume_file, + local.mariadb_data_volume_file, + local.mariadb_container_file, + local.valkey_cache_volume_file, + local.valkey_container_file, + local.castopod_container_file, + ] + directories = [ + local.caddy_config_directory, + local.castopod_secrets_directory, + ] + } + systemd = { + units = [ + local.castopod_secrets_mount_unit, + local.castopod_generate_secrets_service_unit, + ] + } + passwd = { + users = [ + { + name = "core" + sshAuthorizedKeys = var.ssh_authorized_keys + } + ] + } + }) +} diff --git a/modules/castopod/outputs.tf b/modules/castopod/outputs.tf new file mode 100644 index 0000000..3f0fbc2 --- /dev/null +++ b/modules/castopod/outputs.tf @@ -0,0 +1,3 @@ +output "config" { + value = local.ignition_config +} diff --git a/modules/castopod/variables.tf b/modules/castopod/variables.tf new file mode 100644 index 0000000..c2c94d6 --- /dev/null +++ b/modules/castopod/variables.tf @@ -0,0 +1,19 @@ +variable "ssh_authorized_keys" { + type = list(string) + nullable = false +} + +variable "base_url" { + type = string + nullable = false +} + +variable "castopod_domain" { + type = string + nullable = false +} + +variable "castopod_upstream_port" { + type = number + nullable = false +} diff --git a/modules/dns_resolver/.gitignore b/modules/dns_resolver/.gitignore new file mode 100644 index 0000000..1767c0b --- /dev/null +++ b/modules/dns_resolver/.gitignore @@ -0,0 +1,2 @@ +dns_resolver_dhcp_config_*.conf +dns_resolver_sftp_script_for_* diff --git a/modules/dns_resolver/files/dhcp_config.conf.tftpl b/modules/dns_resolver/files/dhcp_config.conf.tftpl new file mode 100644 index 0000000..8fb7fe5 --- /dev/null +++ b/modules/dns_resolver/files/dhcp_config.conf.tftpl @@ -0,0 +1,3 @@ +dhcp-host=${mac_address},set:exampletag,${host_ip},example +dhcp-option=tag:sampletag,encap:128,2,"${vm_id}.ign" +dhcp-option=tag:sampletag,encap:128,3,"/dev/disk/by-path/pci-0000:00:0a.0" diff --git a/modules/dns_resolver/main.tf b/modules/dns_resolver/main.tf new file mode 100644 index 0000000..5854652 --- /dev/null +++ b/modules/dns_resolver/main.tf @@ -0,0 +1,168 @@ +terraform { + required_providers { + proxmox = { + source = "bpg/proxmox" + version = "~>0.56.1" + } + } + required_version = ">=1.6.2" +} + + +locals { + core_user = { + name = "core" + password_hash = "$6$vDMAZf/yOO6mEbcs$6VE7WD8T9/PeotszMFxatOQxB/rFmLDWsNajg4sI0O47OikSuVpqPjkxRbzcueiXn6rBUY1ubCHlp0nnoZ1VI1" + } + + hostname_file = { + path = "/etc/hostname" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode(var.instance_name) + ) + } + } + + ignition_configuration = jsonencode({ + ignition = { + version = "3.4.0" + } + storage = { + files = [ + { + path = "/etc/hostname" + user = {id = 0} + group = {id = 0} + mode = 420 # 0644 + contents = { + source = format( + "data:text/plain;base64,%s", + base64encode(var.instance_name) + ) + } + }, + ] + } + passwd = { + users = [ + local.core_user, + ] + } + }) +} + +resource "random_pet" "config_name" { + length = 4 +} + +locals { + generated_ignition_config_file = "${path.module}/dns_resolver_ignition_config_${random_pet.config_name.id}.ign" +} + +resource "local_file" "sftp_script_for_ignition_file" { + content = <