From 55b3e74aa228b504165eff664f5da6694c277293 Mon Sep 17 00:00:00 2001 From: Matthew Baggett Date: Fri, 14 Jun 2024 04:18:48 +0200 Subject: [PATCH] Educational material go brr --- .dockerignore | 3 + .gitignore | 4 + .terraform.lock.hcl | 42 ++++++++++ README.md | 39 +++++++++ database.tf | 62 ++++++++++++++ docker.tf | 3 + inputs.tf | 7 ++ locals.tf | 15 ++++ outputs.tf | 15 ++++ quassel.tf | 71 ++++++++++++++++ quassel/Dockerfile | 82 +++++++++++++++++++ .../dependencies.d/init-quassel-config | 0 .../dependencies.d/init-config | 0 .../s6-rc.d/init-quassel-config/run | 13 +++ .../s6-rc.d/init-quassel-config/type | 1 + .../s6-overlay/s6-rc.d/init-quassel-config/up | 1 + .../svc-quassel/dependencies.d/init-services | 0 .../s6-rc.d/svc-quassel/notification-fd | 1 + .../etc/s6-overlay/s6-rc.d/svc-quassel/run | 7 ++ .../etc/s6-overlay/s6-rc.d/svc-quassel/type | 1 + .../user/contents.d/init-quassel-config | 0 .../s6-rc.d/user/contents.d/svc-quassel | 0 terraform.tf | 12 +++ terraform.tfvars | 1 + 24 files changed, 380 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .terraform.lock.hcl create mode 100644 database.tf create mode 100644 docker.tf create mode 100644 inputs.tf create mode 100644 locals.tf create mode 100644 outputs.tf create mode 100644 quassel.tf create mode 100644 quassel/Dockerfile create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-quassel-config create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/dependencies.d/init-config create mode 100755 quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/run create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/type create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/up create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/dependencies.d/init-services create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/notification-fd create mode 100755 quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/run create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/type create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-quassel-config create mode 100644 quassel/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-quassel create mode 100644 terraform.tf create mode 100644 terraform.tfvars diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..26a2e82 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +/*.tf* +/.git +/.terraform diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0afff3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.idea +/.terraform +/terraform.tfstate* +/.terraform.tfstate* diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl new file mode 100644 index 0000000..3f59188 --- /dev/null +++ b/.terraform.lock.hcl @@ -0,0 +1,42 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/random" { + version = "3.6.2" + constraints = "~> 3.3" + hashes = [ + "h1:PXvoOj9gj+Or+9k0tQWCQJKxnsVO0GqnQwVahgwRrsU=", + "zh:1f27612f7099441526d8af59f5b4bdcc35f46915df5d243043d7337ea5a3e38a", + "zh:2a58e66502825db8b4b96116c04bd0323bca1cf1f5752bdd8f9c26feb84d3b1e", + "zh:4f0a4fa479e29de0c3c90146fd58799c097f7a55401cb00560dd4e9b1e6fad9d", + "zh:9c93c0fe6ef685513734527e0c8078636b2cc07591427502a7260f4744b1af1d", + "zh:a466ff5219beb77fb3b18a3d7e7fe30e7edd4d95c8e5c87f4f4e3fe3eeb8c2d7", + "zh:ab33e6176d0c757ddb31e40e01a941e6918ad10f7a786c8e8e4f35e5cff81c96", + "zh:b6eabf377a1c12cb3f9ddd97aacdd5b49c1646dc959074124f81d40fcd216d7e", + "zh:ccec5d03d0d1c0f354be299cdd6a417b2700f1a6781df36bcce77246b2f57e50", + "zh:d2a7945eeb691fdd2b1474da76ddc2d1655e2aedbb14b57f06d4f5123d47adf9", + "zh:ed62351f4ad9d1469c6798b77dee5f63b18b29c473620a0046ba3d4f111b621d", + ] +} + +provider "registry.opentofu.org/kreuzwerker/docker" { + version = "3.0.2" + constraints = "~> 3.0" + hashes = [ + "h1:cT2ccWOtlfKYBUE60/v2/4Q6Stk1KYTNnhxSck+VPlU=", + "zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f", + "zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95", + "zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138", + "zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da", + "zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2", + "zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06", + "zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1", + "zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710", + "zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005", + "zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d", + "zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792", + "zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387", + "zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62", + "zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d", + ] +} diff --git a/README.md b/README.md index e69de29..6ee48f6 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,39 @@ +Example Dockerised, Terraformed application deployment onto docker on bare metal +=============================================================================== + +We're gonna run Quassel on bare metal in the most low-effort and high-reward enterprisey way possible. +Why? Because we can. + +## Pre-requisites +We're gonna need some tools: + +* OpenTofu, the replacement for Terraform since Hashicorp changed the licence terms: https://opentofu.org/docs/intro/install/. + * As Tofu is a drop in replacement for Terraform, you can use Terraform if you prefer, and I'll refer to them interchangeably. + +## What have we got going on in here? +The main players: + * database.tf - Configures our Quassel instances Postgres + * quassel.tf - Configures our Quassel instance itself + +The bit-actors: + * docker.tf - Configures the Docker Provider for Tofu + * inputs.tf - Where inputs to this deployment are defined and may have defaults set + * outputs.tf - Where outputs from this deployment are defined, which will be output when we run `tofu apply` or `tofu outputs` + * terraform.tf - Telling Terraform/ToFu what packages we need to run this deployment + * .terraform.lock.hcl - Lock file for Terraform/ToFu, this is here to ensure that we're all using the same versions of the same packages + * terraform.tfvars - Where we may set the variables defined in inputs.tf + +## What happens when we run this? +When we run `tofu apply`: + * Tofu will create a Docker network for our Quassel instance + * Tofu will create a Postgres instance for our Quassel instance + * Tofu will compile our Quassel instance image from the Dockerfile in the quassel directory + * Tofu will create a Quassel instance using that image + * Tofu will output the IP address of the Quassel instance and postgres instance + +## How do we run this? +Did you give `tofu apply` a go yet? + +## What we're gonna end up with: + +`DIAGRAM GOES HERE` diff --git a/database.tf b/database.tf new file mode 100644 index 0000000..3496c0a --- /dev/null +++ b/database.tf @@ -0,0 +1,62 @@ +# Find our latest postgres 16 image +data "docker_registry_image" "postgres_quassel" { + name = "postgres:16" +} + +# Generate a random password for our database +resource "random_password" "quassel_db_password" { + length = 32 + special = false +} + +# Create a volume for our database data to live in +resource "docker_volume" "quassel_db" { + name = "${var.docker_prefix}-quassel-db" +} + +# Create our database service +resource "docker_service" "quassel_db" { + name = "${var.docker_prefix}-quassel-db" + task_spec { + container_spec { + # We've got our image from the registry... + image = "${data.docker_registry_image.postgres_quassel.name}@${data.docker_registry_image.postgres_quassel.sha256_digest}" + # And we're going to set some environment variables + env = { + POSTGRES_USER = local.pg_username + POSTGRES_DB = local.pg_database + POSTGRES_PASSWORD = local.pg_password + } + # We're going to define a nice healthcheck that will check that postgres is alive and well + healthcheck { + # Effectively this is running 'pg_isready -d postgres -U postgres' on the commandline inside the container and if it returns 0, the container is healthy, anything else is failure + test = ["CMD-SHELL", "pg_isready", "-d", local.pg_database, "-U", local.pg_username] + interval = "5s" + start_period = "15s" + } + # And we're going to mount our data volume to the container so that the data persists between restarts + mounts { + target = "/var/lib/postgresql/data" + type = "volume" + source = docker_volume.quassel_db.id + } + } + # And attach our network so that the quassel service can talk to the database + networks_advanced { + name = docker_network.quassel.id + } + } + # And we're going to wait for it to be up and running before we move on + converge_config { + delay = "5s" # Wait 5 seconds between checks + timeout = "2m" # Give up after 2 minutes + } + endpoint_spec { + ports { + target_port = local.pg_port_internal + published_port = local.pg_port_external + protocol = "tcp" + publish_mode = "ingress" + } + } +} diff --git a/docker.tf b/docker.tf new file mode 100644 index 0000000..fcad5df --- /dev/null +++ b/docker.tf @@ -0,0 +1,3 @@ +provider "docker" { + host = "ssh://california.ti" +} diff --git a/inputs.tf b/inputs.tf new file mode 100644 index 0000000..e4d3f24 --- /dev/null +++ b/inputs.tf @@ -0,0 +1,7 @@ +variable "docker_prefix" { + description = "Prefix for all docker resources.. We're not alone on this box, so we best avoid collisions" +} +variable "tz" { + description = "Timezone for the server" + default = "Europe/Amsterdam" +} diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..b33691d --- /dev/null +++ b/locals.tf @@ -0,0 +1,15 @@ +locals { + # Lets just take a moment to make some ease of life variables + + # Quassel + quassel_port = random_integer.quassel_port.result # Lets just put the port in a variable so we can use it in multiple places + + # database + pg_username = "postgres" + pg_database = "postgres" + pg_password = random_password.quassel_db_password.result + pg_hostname = docker_service.quassel_db.name + # We're going to use the quassel port + 1 for the postgres port because why not.. But this is only for external access, inside the internal network it will still be 5432! + pg_port_internal = 5432 + pg_port_external = local.quassel_port + 1 +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..ce4a1df --- /dev/null +++ b/outputs.tf @@ -0,0 +1,15 @@ +output "quassel" { + value = { + hostname = docker_service.quassel_db.name + port = local.quassel_port + } +} +output "postgres" { + value = { + hostname = docker_service.quassel_db.name + port = local.pg_port_external + username = local.pg_username + password = nonsensitive(local.pg_password) + database = local.pg_database + } +} diff --git a/quassel.tf b/quassel.tf new file mode 100644 index 0000000..f45794c --- /dev/null +++ b/quassel.tf @@ -0,0 +1,71 @@ +# Pick a random port to use for our uplink port. +resource "random_integer" "quassel_port" { + max = 65535 + min = 1024 +} + +# Build our latest quassel docker image. +resource "docker_image" "quassel" { + name = "${var.docker_prefix}-quassel" + build { + context = "${path.module}/quassel" + } + triggers = { + dir_sha1 = sha1(join("", [for f in fileset(path.module, "quassel/*") : filesha1(f)])) + } +} + +# Create a network for our quassel service and postgres service to communicate upon +resource "docker_network" "quassel" { + name = "${var.docker_prefix}-quassel" + driver = "overlay" # We're using overlay networking because its fuckin' rad. +} + +# Create our Quassel docker service. +resource "docker_service" "quassel" { + name = "${var.docker_prefix}-quassel" + + # We need the database to be present for this container to work, so we can explicitly tell TF about it here + depends_on = [docker_service.quassel_db] + + # We're going to define the task specification + task_spec { + # Which contains a container specification + container_spec { + # Which has a docker image set + #image = "${data.docker_registry_image.quassel.name}@${data.docker_registry_image.quassel.sha256_digest}" + image = docker_image.quassel.name + env = { + # And a bunch of environment variables as per the upstream documentation. + PUID = 1000 + PGID = 1000 + TZ = var.tz + RUN_OPTS = "--config-from-environment" + DB_BACKEND = "PostgreSQL" + DB_PGSQL_USERNAME = local.pg_username + DB_PGSQL_PASSWORD = local.pg_password + DB_PGSQL_HOSTNAME = local.pg_hostname + DB_PGSQL_PORT = local.pg_port_internal + AUTH_AUTHENTICATOR = "Database" + } + } + # Attach our task to the network we created earlier + networks_advanced { + name = docker_network.quassel.id + } + } + # Setting a converge config means that we will wait for the service to be up and running (and reporting it is healthy) before moving on. + converge_config { + delay = "5s" # Wait 5 seconds between checks + timeout = "2m" # Give up after 2 minutes + } + endpoint_spec { + # Configure our service to listen on a random port on the ingress network (which means any node in the swarm will redirect the traffic to (an instance of) this service) + ports { + target_port = 4242 # default quassel port on the container + published_port = local.quassel_port # Use the random port we generated earlier + protocol = "tcp" + publish_mode = "ingress" # Its that fwicked cool sweet awesome overlay network again, but this time ingress from the outside of the cluster + } + } +} diff --git a/quassel/Dockerfile b/quassel/Dockerfile new file mode 100644 index 0000000..143845f --- /dev/null +++ b/quassel/Dockerfile @@ -0,0 +1,82 @@ +FROM ghcr.io/linuxserver/baseimage-alpine:3.18 as build-stage + +# build time arguements +ARG CXXFLAGS="\ + -D_FORTIFY_SOURCE=2 \ + -Wp,-D_GLIBCXX_ASSERTIONS \ + -fstack-protector-strong \ + -fPIE -pie -Wl,-z,noexecstack \ + -Wl,-z,relro -Wl,-z,now" +ARG QUASSEL_RELEASE +# install build packages +RUN \ + apk add --no-cache \ + boost-dev \ + build-base \ + cmake \ + dbus-dev \ + icu-dev \ + openssl-dev \ + openldap-dev \ + qt5-qtbase-dev \ + qt5-qtscript-dev \ + qt5-qtbase-postgresql \ + qt5-qtbase-sqlite \ + qca-dev \ + zlib-dev + +# fetch source +RUN \ + mkdir -p \ + /tmp/quassel-src/build && \ + if [ -z ${QUASSEL_RELEASE+x} ]; then \ + QUASSEL_RELEASE=$(curl -sX GET "https://api.github.com/repos/quassel/quassel/releases/latest" \ + | jq -r .tag_name); \ + fi && \ + curl -o \ + /tmp/quassel.tar.gz -L \ + "https://github.com/quassel/quassel/archive/${QUASSEL_RELEASE}.tar.gz" && \ + tar xf \ + /tmp/quassel.tar.gz -C \ + /tmp/quassel-src --strip-components=1 + +# build package +RUN \ + cd /tmp/quassel-src/build && \ + cmake \ + -DCMAKE_BUILD_TYPE="Release" \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DWANT_CORE=ON \ + -DWANT_MONO=OFF \ + -DWANT_QTCLIENT=OFF \ + -DWITH_KDE=OFF \ + /tmp/quassel-src && \ + make -j2 && \ + make DESTDIR=/build/quassel install + +FROM ghcr.io/linuxserver/baseimage-alpine:3.18 + +# set environment variables +ENV HOME /config + +# install runtime packages +RUN \ + apk add --no-cache \ + icu-libs \ + openssl \ + qt5-qtbase \ + qt5-qtbase-postgresql \ + qt5-qtbase-sqlite \ + qt5-qtscript \ + libqca \ + openldap + +# copy artifacts build stage +COPY --from=build-stage /build/quassel/usr/ /usr/ + +# add local files +COPY root/ / + +# ports and volumes +VOLUME /config +EXPOSE 4242 diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-quassel-config b/quassel/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-quassel-config new file mode 100644 index 0000000..e69de29 diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/dependencies.d/init-config b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/dependencies.d/init-config new file mode 100644 index 0000000..e69de29 diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/run b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/run new file mode 100755 index 0000000..912a086 --- /dev/null +++ b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/run @@ -0,0 +1,13 @@ +#!/usr/bin/with-contenv bash +# shellcheck shell=bash + +# generate key +if [[ ! -f /config/quasselCert.pem ]]; then + openssl req -x509 -nodes -days 3650 \ + -newkey rsa:4096 -keyout /config/quasselCert.pem -out /config/quasselCert.pem \ + -subj "/CN=Quassel-core" +fi + +# permissions +lsiown -R abc:abc \ + /config diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/type b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/type new file mode 100644 index 0000000..bdd22a1 --- /dev/null +++ b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/type @@ -0,0 +1 @@ +oneshot diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/up b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/up new file mode 100644 index 0000000..61251f0 --- /dev/null +++ b/quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-quassel-config/run diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/dependencies.d/init-services b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/dependencies.d/init-services new file mode 100644 index 0000000..e69de29 diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/notification-fd b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/notification-fd new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/notification-fd @@ -0,0 +1 @@ +3 diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/run b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/run new file mode 100755 index 0000000..f3679f5 --- /dev/null +++ b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/run @@ -0,0 +1,7 @@ +#!/usr/bin/with-contenv bash +# shellcheck shell=bash + +exec \ + s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost 4242" \ + s6-setuidgid abc quasselcore \ + --configdir /config ${RUN_OPTS} diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/type b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/type new file mode 100644 index 0000000..5883cff --- /dev/null +++ b/quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/type @@ -0,0 +1 @@ +longrun diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-quassel-config b/quassel/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-quassel-config new file mode 100644 index 0000000..e69de29 diff --git a/quassel/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-quassel b/quassel/root/etc/s6-overlay/s6-rc.d/user/contents.d/svc-quassel new file mode 100644 index 0000000..e69de29 diff --git a/terraform.tf b/terraform.tf new file mode 100644 index 0000000..448ac0b --- /dev/null +++ b/terraform.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~>3.0" + } + random = { + source = "hashicorp/random" + version = "~>3.3" + } + } +} diff --git a/terraform.tfvars b/terraform.tfvars new file mode 100644 index 0000000..5281e56 --- /dev/null +++ b/terraform.tfvars @@ -0,0 +1 @@ +docker_prefix = "example-deployable"