commit 50d7f968c7ca6bbfaa35736ede1da57f2a4a3ef7
Author: root <root>
Date: Sun, 15 Mar 2026 19:58:08 +0100
initial commit
Diffstat:
29 files changed, 746 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,12 @@
+terraform-template/.terraform/
+terraform-template/terraform.tfstate
+terraform-template/terrafor,.tfstate.*
+terraform-template/*.tfstate
+terraform-template/*.tfstate.backup
+terraform-template/locals.tf.last
+terraform-template/locals.tf
+terraform-template/boundary.txt
+terraform-template/boundary.mk
+terraform-template/dump/00_dump.sql
+terraform-template/dump/gtid_pos.txt
+batches/
diff --git a/Makefile b/Makefile
@@ -0,0 +1,40 @@
+-include config.mk
+BATCH_NAME ?= batch-$(shell date +%Y%m%d-%H%M%S)
+THIS_BATCH := $(BATCH_DIR)/$(BATCH_NAME)
+WORKING_DIR := $(THIS_BATCH)/terraform-template
+NUMBER ?= 2
+PORT ?= 3313
+
+help:
+ @echo "Batch Deployment Orchestrator"
+ @echo ""
+ @echo "Targets:"
+ @echo " make new [BATCH_NAME=name] - Create new batch"
+ @echo " make deploy BATCH_NAME=name [NUMBER=2] - Deploy batch"
+ @echo " make destroy BATCH_NAME=name [NUMBER=2] - Destroy batch"
+ @echo " make list - List all batches"
+ @echo " make clean BATCH_NAME=name - Delete batch directory"
+ @echo ""
+ @echo "Examples:"
+ @echo " make new BATCH_NAME=batch-1"
+ @echo " make deploy BATCH_NAME=batch-1"
+ @echo " make destroy BATCH_NAME=batch-1"
+
+new:
+ @bash $(SCRIPTS_DIR)/new_batch.sh $(BATCH_NAME) $(THIS_BATCH) $(BATCH_DIR) $(TEMPLATE_DIR)
+
+deploy:
+ @bash $(SCRIPTS_DIR)/deploy_to_new_batch.sh $(BATCH_NAME) $(THIS_BATCH) $(WORKING_DIR) $(NUMBER) $(PORT) $(TEMPLATE_DIR) $(BATCH_REGISTRY)
+
+destroy:
+ @bash $(SCRIPTS_DIR)/destroy_in_batch.sh $(BATCH_NAME) $(THIS_BATCH) $(WORKING_DIR) $(NUMBER) $(TEMPLATE_DIR) $(BATCH_REGISTRY)
+
+list:
+ @bash $(SCRIPTS_DIR)/list_batches.sh $(BATCH_NAME) $(THIS_BATCH) $(BATCH_DIR) $(TEMPLATE_DIR) $(BATCH_REGISTRY)
+
+clean:
+ @bash $(SCRIPTS_DIR)/delete_batch_dir.sh $(BATCH_NAME) $(THIS_BATCH) $(TEMPLATE_DIR) $(BATCH_REGISTRY)
+
+.PHONY: new deploy destroy list clean help
+
+.DEFAULT_GOAL := help
diff --git a/README.md b/README.md
@@ -0,0 +1,25 @@
+# What is this?
+
+This repository contains a small project that does two things:
+
+* -> create terraform managed docker containers with mariadb, and add them as read-only to an existing maxscale cluster
+* -> remove terraform managed mariadb docker containers from maxscale, and destroy the containers
+
+## Assumptions:
+
+* -> Maxscale already exists on the machine where terraform is executed from
+* -> A Master server, and 2 Replica servers are already plugged into maxscale, users for maxscale and replication already set up
+* -> Terraform does not manage these 3 initial servers, it just needs to know how to add extra servers on top
+* -> Naming scheme of servers is just "server1, server2, server3" etc.
+* -> It is OK to just restart maxscale, in reality there would be a HA mechanism, but not in this homelab
+
+## Requirements:
+
+### HOST
+
+- MariaDB (or just mariadb-client with access to a master server), Maxscale, Make, Terraform, gnu awk, gnu sed, bash, ssh access to target server
+
+### TARGET SERVER
+
+- docker
+
diff --git a/config.mk b/config.mk
@@ -0,0 +1,5 @@
+ROOT_SRC=$(CURDIR)
+TEMPLATE_DIR=$(ROOT_SRC)/terraform-template
+BATCH_DIR=$(ROOT_SRC)/batches
+BATCH_REGISTRY=$(BATCH_DIR)/batch_registry.txt
+SCRIPTS_DIR=$(ROOT_SRC)/scripts
diff --git a/scripts/delete_batch_dir.sh b/scripts/delete_batch_dir.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+BATCH_NAME=$1
+THIS_BATCH=$2
+TEMPLATE_DIR=$3
+BATCH_REGISTRY=$4
+
+if [ ! -d "${THIS_BATCH}" ]; then
+ echo "Error: Batch ${BATCH_NAME} not found!"
+ exit 1;
+fi
+echo "Removing batch directory: ${THIS_BATCH}"
+read -p "Are you sure? [y/N] " CONFIRM;
+if [ "$CONFIRM" = "y" ] || [ "$CONFIRM" = "Y" ]; then
+ rm -rf ${THIS_BATCH};
+ sed -i "/^${BATCH_NAME}:/d" ${BATCH_REGISTRY}
+ echo "Batch removed!";
+else
+ echo "Cancelled";
+fi
diff --git a/scripts/deploy_to_new_batch.sh b/scripts/deploy_to_new_batch.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+BATCH_NAME=$1
+THIS_BATCH=$2
+WORKING_DIR=$3
+NUMBER=$4
+PORT=$5
+TEMPLATE_DIR=$6
+BATCH_REGISTRY=$7
+
+if [ ! -d "${THIS_BATCH}" ]; then
+ echo "Error: Batch ${BATCH_NAME} not found"
+ exit 1
+fi
+
+if [ ! -d "${WORKING_DIR}" ]; then
+ echo "Error: Batch ${BATCH_NAME} has no template dir!"
+ exit 1
+fi
+
+echo "Deploying batch: ${BATCH_NAME} with ${NUMBER} servers"
+cd ${WORKING_DIR} && make OPERATION=APPLY NUMBER=${NUMBER} PORT=${PORT} BATCH_ID=${BATCH_NAME}
+echo "${BATCH_NAME}: ${NUMBER} servers" >> ${BATCH_REGISTRY}
+echo "Batch deployed successfully!"
diff --git a/scripts/destroy_in_batch.sh b/scripts/destroy_in_batch.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+BATCH_NAME=$1
+THIS_BATCH=$2
+WORKING_DIR=$3
+NUMBER=$4
+TEMPLATE_DIR=$5
+BATCH_REGISTRY=$6
+
+if [ ! -d "${THIS_BATCH}" ]; then
+ echo "Error: Batch ${BATCH_NAME} not found"
+ exit 1
+fi
+
+if [ ! -d "${WORKING_DIR}" ]; then
+ echo "Error: Batch ${BATCH_NAME} has no template dir!"
+ exit 1
+fi
+
+echo "Destroying batch: ${BATCH_NAME}"
+cd ${WORKING_DIR} && make OPERATION=DESTROY NUMBER=${NUMBER}
+sed -i "/^${BATCH_NAME}:/d" ${BATCH_REGISTRY}
+echo "Batch destroyed successfully!"
diff --git a/scripts/list_batches.sh b/scripts/list_batches.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+BATCH_NAME=$1
+THIS_BATCH=$2
+BATCH_DIR=$3
+TEMPLATE_DIR=$4
+BATCH_REGISTRY=$5
+
+echo "[=== Deployed Batches ===]"
+cat $BATCH_REGISTRY | awk -v dir="$(echo $BATCH_DIR/)" 'BEGIN{FS=":"}{print "echo "$1" && cat "dir$1"/terraform-template/created_servers.txt"}' | bash
diff --git a/scripts/new_batch.sh b/scripts/new_batch.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+BATCH_NAME=$1
+THIS_BATCH=$2
+BATCH_DIR=$3
+TEMPLATE_DIR=$4
+
+echo "Creating batch: ${BATCH_NAME}"
+if [ -d "${THIS_BATCH}" ]; then
+ echo "Error: Batch ${BATCH_NAME} already exists"
+ exit 1
+fi
+
+mkdir -p "$BATCH_DIR" && mkdir -p "$THIS_BATCH"
+
+cp -r ${TEMPLATE_DIR} ${THIS_BATCH}
+cd ${THIS_BATCH} && rm -f terraform.tfstate* .terraform.lock.hcl boundary.txt boundary.mk boundary.txt.last locals.tf locals.tf.last created_servers.txt dump/00_dump.sql dump/gtid_pos.txt
+cd ${THIS_BATCH} && rm -rf .terraform/
+echo "Batch created @ ${THIS_BATCH}"
diff --git a/terraform-template/.terraform.lock.hcl b/terraform-template/.terraform.lock.hcl
@@ -0,0 +1,43 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/local" {
+ version = "2.7.0"
+ constraints = "~> 2.0"
+ hashes = [
+ "h1:2RYa3j7m/0WmET2fqotY4CHxE1Hpk0fgn47/126l+Og=",
+ "zh:261fec71bca13e0a7812dc0d8ae9af2b4326b24d9b2e9beab3d2400fab5c5f9a",
+ "zh:308da3b5376a9ede815042deec5af1050ec96a5a5410a2206ae847d82070a23e",
+ "zh:3d056924c420464dc8aba10e1915956b2e5c4d55b11ffff79aa8be563fbfe298",
+ "zh:643256547b155459c45e0a3e8aab0570db59923c68daf2086be63c444c8c445b",
+ "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
+ "zh:7aa4d0b853f84205e8cf79f30c9b2c562afbfa63592f7231b6637e5d7a6b5b27",
+ "zh:7dc251bbc487d58a6ab7f5b07ec9edc630edb45d89b761dba28e0e2ba6b1c11f",
+ "zh:7ee0ca546cd065030039168d780a15cbbf1765a4c70cd56d394734ab112c93da",
+ "zh:b1d5d80abb1906e6c6b3685a52a0192b4ca6525fe090881c64ec6f67794b1300",
+ "zh:d81ea9856d61db3148a4fc6c375bf387a721d78fc1fea7a8823a027272a47a78",
+ "zh:df0a1f0afc947b8bfc88617c1ad07a689ce3bd1a29fd97318392e6bdd32b230b",
+ "zh:dfbcad800240e0c68c43e0866f2a751cff09777375ec701918881acf67a268da",
+ ]
+}
+
+provider "registry.terraform.io/kreuzwerker/docker" {
+ version = "3.6.2"
+ constraints = "~> 3.0"
+ hashes = [
+ "h1:1K3j0xUY2D0+E+DBDQc6k1u6Al9MkuNWrIC9rnvwFSM=",
+ "zh:22b51a8fb63481d290bdad9a221bc8c9e45d66d1a0cd45beed3f3627bf1debd8",
+ "zh:2b902eb80a1ae033af1135cc165d192668820a7f8ea15beb5472f811c18bea1f",
+ "zh:57815dcea28aedb86ed33924cd186aaee8bd31670bd78437a2a2daf2b00ce2ae",
+ "zh:583af9c6fe7e3bfc04f50aec046a9b4f98b7eddd6d1e143454e5d06a66afcf87",
+ "zh:80f8cba54f639a53c4d7714edb7246064b7f4f48ba93a70f18c914d656d799db",
+ "zh:894709f0c393c4ee91fdb849128e7f0bce688f293cd1643a6d4e39c842367278",
+ "zh:a91b41dbcb203d6dae2bb72b98c4c21c41255026b35df01895882784c4650071",
+ "zh:aec40a8157aae093412a1fb9a71ab2bea370db152e285c2d81e37ed378444b9c",
+ "zh:b87d7def2485dde6e57723c1265158f371440a8a84954c9fdb0580cf89de66bf",
+ "zh:b9dc243200ad9cd00250cb8c793ecea4ee3c57a121faf8efdb289f30008b5778",
+ "zh:dcb103831db6d3ef95468685cd104be3928793996542a1f675dc34a2ce67951d",
+ "zh:e59b4a0f2b5881016896d4417b1ab2fb87f34450663efeb01f3bcf7c3606fbbb",
+ "zh:fbd068c01114f0712578cf02f363b5521338ab1befedddf7090da532298b43d0",
+ ]
+}
diff --git a/terraform-template/Makefile b/terraform-template/Makefile
@@ -0,0 +1,67 @@
+-include config.mk
+-include boundary.mk
+OPERATION ?=
+VALID_OPERATIONS := APPLY DESTROY
+NUMBER ?=
+PORT ?=
+BATCH_ID ?=
+
+ifeq ($(NUMBER),)
+ NUMBER = 2
+endif
+
+ifeq ($(PORT),)
+ PORT = 3313
+endif
+
+ifeq ($(BATCH_ID),)
+ BATCH_ID = 0
+endif
+
+ifneq ($(OPERATION),APPLY)
+ ifneq ($(OPERATION),DESTROY)
+ $(error Invalid value for OPERATION: "$(OPERATION)". Must be "APPLY" or "DESTROY")
+ endif
+endif
+
+IS_MAXSCALE_ACTIVE := $(shell systemctl is-active maxscale)
+
+ifneq ($(IS_MAXSCALE_ACTIVE),active)
+$(error Maxscale service is not running, can not do service discovery)
+endif
+
+ifeq ($(OPERATION),APPLY)
+ TARGETS := build apply
+else ifeq ($(OPERATION),DESTROY)
+ TARGETS := destroy
+endif
+
+operate: service_discovery
+ $(MAKE) execute_operation
+#we need a dynamically generated value from a file, so we must call make from make to refresh state
+
+execute_operation:
+ $(MAKE) $(TARGETS)
+
+#checks maxctrl, tells what's currently running gives the number of current servers, and builds a locals.tf file
+service_discovery:
+ rm -f boundary.mk
+ bash service_discovery.sh $(NUMBER) $(PORT) $(OPERATION) $(ROOT_SRC)
+ @echo "BOUNDARY=$$(cat boundary.txt)" > boundary.mk
+
+#builds and transports the docker images to the target server, along with a fresh mysqldump
+build:
+ bash init.sh $(TARGET_SRV) $(ROOT_SRC) $(REMOTE_DOCKER_DIR) $(REMOTE_DOCKER_SCRIPTS_DIR) $(DOCKER_BUILD)
+
+#runs the new docker containers, updates maxscale
+apply:
+ terraform init
+ terraform apply -var="target_ip=$(TARGET_SRV)" -var="docker_image=$(DOCKER_BUILD)" -var="batch_id=$(BATCH_ID)"
+ bash create_maxscale.sh $(BOUNDARY) $(NUMBER) $(PORT) $(ROOT_SRC) $(TARGET_SRV)
+
+#destroys the docker containers, updates maxscale
+destroy:
+ bash destroy_maxscale.sh $(ROOT_SRC)
+ terraform destroy -var="target_ip=$(TARGET_SRV)" -var="docker_image=$(DOCKER_BUILD)"
+
+.PHONY: service_discovery execute_operation operate build apply destroy
diff --git a/terraform-template/README.md b/terraform-template/README.md
@@ -0,0 +1,25 @@
+# What is this?
+
+This repository contains a small project that does two things:
+
+* -> create terraform managed docker containers with mariadb, and add them as read-only to an existing maxscale cluster
+* -> remove terraform managed mariadb docker containers from maxscale, and destroy the containers
+
+## Assumptions:
+
+* -> Maxscale already exists on the machine where terraform is executed from
+* -> A Master server, and 2 Replica servers are already plugged into maxscale, users for maxscale and replication already set up
+* -> Terraform does not manage these 3 initial servers, it just needs to know how to add extra servers on top
+* -> Naming scheme of servers is just "server1, server2, server3" etc.
+* -> It is OK to just restart maxscale, in reality there would be a HA mechanism, but not in this homelab
+
+## Requirements:
+
+### HOST
+
+- MariaDB (or just mariadb-client with access to a master server), Maxscale, Make, Terraform, gnu awk, gnu sed, bash, ssh access to target server
+
+### TARGET SERVER
+
+- docker
+
diff --git a/terraform-template/awk_scripts/create_maxscale.awk b/terraform-template/awk_scripts/create_maxscale.awk
@@ -0,0 +1,44 @@
+#!/bin/awk -f
+
+BEGIN{
+ found = 0;
+ if(boundary == "") {boundary = 4;}
+ if(number == "") {number = 2;}
+ if(port == "") {port = 3313;}
+ if(address == "") {address="192.168.2.99";}
+ pat="\\[server"(boundary-1)"\\]";
+ pat2="";
+ repl="";
+ for(i=boundary-2; i<boundary; i++) {
+ pat2=pat2"server"i",";
+ }
+ pat2=substr(pat2, 1, length(pat2) - 1);
+ for(i=boundary; i<boundary+number; i++) {
+ repl=repl",server"i;
+ }
+}
+{
+ if($0 ~ pat2) {
+ sub(pat2, pat2""repl, $0);
+ print $0;
+ } else {
+ print $0;
+ }
+ if($0 ~ pat) {
+ found = 1;
+ }
+ if((found == 1) && ($0 ~ /protocol=MariaDBBackend/)) {
+ for(i = boundary; i < (boundary+number); i++) {
+ port++;
+ print "\n[server"i"]";
+ print "type=server";
+ print "address="address;
+ print "port="port;
+ print "protocol=MariaDBBackend\n";
+ }
+ found = 0;
+ next;
+ }
+}
+END{
+}
diff --git a/terraform-template/awk_scripts/destroy_maxscale.awk b/terraform-template/awk_scripts/destroy_maxscale.awk
@@ -0,0 +1,38 @@
+#!/bin/awk -f
+
+BEGIN{
+ if(number == "") {exit 1;}
+ if(boundary == "") {exit 1;}
+ startpos = boundary - number;
+ endpos = boundary - 1;
+ skip = 0;
+ blank_seen = 0;
+
+ for (i = startpos; i <= endpos; i++) {
+ managed["[server"i"]"] = 1;
+ }
+
+}
+{
+ if(skip > 0) {
+ skip--;
+ next;
+ }
+ if($0 in managed) {
+ skip = 4;
+ next;
+ }
+
+ if ($0 ~ /^[[:space:]]*$/) {
+ if (blank_seen) {
+ next;
+ }
+ blank_seen = 1;
+ print $0;
+ next;
+ }
+
+ blank_seen = 0;
+ print $0;
+}
+
diff --git a/terraform-template/awk_scripts/port_check.awk b/terraform-template/awk_scripts/port_check.awk
@@ -0,0 +1,17 @@
+#!/bin/awk -f
+
+BEGIN{
+ FS="│";
+ max_port = 0;
+}
+{
+ if($4 ~ /[0-9]+/) {
+ match($4, /[0-9]+/, port);
+ if (port[0] > max_port) {
+ max_port = port[0];
+ }
+ }
+}
+END{
+ print max_port;
+}
diff --git a/terraform-template/awk_scripts/service_discovery.awk b/terraform-template/awk_scripts/service_discovery.awk
@@ -0,0 +1,32 @@
+#!/bin/awk -f
+
+BEGIN{
+ FS="│";
+ max_id = 0;
+ if (port == ""){port = 3313;}
+ if (number == ""){number = 2;}
+}
+{
+ if($2 ~ /server[0-9]+/){
+ for (i=1; i<=NF; i++) {
+ gsub(/^[ \t]+|[ \t]+$/, "", $i);
+ }
+ match($2, /([0-9]+)$/, current_id);
+ if (current_id[1] > max_id) {
+ max_id = current_id[1];
+ }
+ }
+}
+END{
+ boundary = max_id+1;
+ print boundary;
+ print "locals {";
+ print " servers = {";
+ for(i = boundary; i < boundary+number; i++) {
+ port++;
+ print " \"server"i"\" = { port = "port", server_id = "i" },";
+ }
+ print " }"
+ print "}";
+}
+
diff --git a/terraform-template/boundary.txt.last b/terraform-template/boundary.txt.last
@@ -0,0 +1 @@
+4
diff --git a/terraform-template/config.mk b/terraform-template/config.mk
@@ -0,0 +1,5 @@
+ROOT_SRC=$(CURDIR)
+TARGET_SRV="192.168.2.99"
+REMOTE_DOCKER_DIR=/opt/docker/mariadb-pkg-tf
+REMOTE_DOCKER_SCRIPTS_DIR=$(REMOTE_DOCKER_DIR)/docker-entrypoint-initdb.d
+DOCKER_BUILD="mariadb-pkg-tf:dev"
diff --git a/terraform-template/config.mk.example b/terraform-template/config.mk.example
@@ -0,0 +1,5 @@
+ROOT_SRC=$(CURDIR)
+TARGET_SRV="{{IP OF SERVER WHERE DOCKER CONTAINERS SHOULD BE CREATED}}"
+REMOTE_DOCKER_DIR={{/path/to/directory/with/dockerfile}}
+REMOTE_DOCKER_SCRIPTS_DIR=$(REMOTE_DOCKER_DIR)/docker-entrypoint-initdb.d
+DOCKER_BUILD="{{docker_image:tag}}"
diff --git a/terraform-template/create_maxscale.sh b/terraform-template/create_maxscale.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+BOUNDARY=$1
+NUMBER=$2
+PORT=$3
+ROOT_SRC=$4
+TARGET_SRV=$5
+TMP=$(mktemp)
+CREATED_SERVERS_FILE=$ROOT_SRC/created_servers.txt
+
+if [[ $BOUNDARY -lt 4 ]]; then
+ exit 1
+fi
+
+CREATED_SERVERS_VAR=""
+ENDPOINT=$(echo "$BOUNDARY + $NUMBER" | bc)
+for ((i = $BOUNDARY; i < $ENDPOINT; i++ )); do
+ CREATED_SERVERS_VAR="${CREATED_SERVERS_VAR}server${i},"
+done
+
+echo "$CREATED_SERVERS_VAR" | sed 's/,$//' > $CREATED_SERVERS_FILE
+
+awk -v boundary=$BOUNDARY -v number=$NUMBER -v port=$PORT -v address=$TARGET_SRV -f $ROOT_SRC/awk_scripts/create_maxscale.awk /etc/maxscale.cnf > $TMP
+cat /etc/maxscale.cnf > /etc/maxscale.cnf.last
+cat $TMP > /etc/maxscale.cnf
+
+systemctl restart maxscale
+
+rm $TMP
diff --git a/terraform-template/created_servers.txt b/terraform-template/created_servers.txt
@@ -0,0 +1 @@
+server4,server5
diff --git a/terraform-template/destroy_maxscale.sh b/terraform-template/destroy_maxscale.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+ROOT_SRC=$1
+
+if [[ ! -s "$ROOT_SRC/created_servers.txt" ]]; then
+ echo "No created servers file found, aborting"
+ exit 1
+fi
+
+MANAGED_SERVERS=$(awk '(NR==1){print $0}' $ROOT_SRC/created_servers.txt)
+echo "DELETING SERVERS ${MANAGED_SERVERS} FROM maxscale.cnf"
+LAST_SERVER_ID=$(echo $MANAGED_SERVERS | grep -Eo 'server[0-9]+$' | sed 's/server//')
+NEXT_SERVER_ID=$(echo "$LAST_SERVER_ID + 1" | bc)
+NUMBER=$(awk 'BEGIN{FS=","}{if(NR==1){print NF}}' $ROOT_SRC/created_servers.txt)
+
+if [[ $LAST_SERVER_ID -lt 4 ]]; then
+ exit 1
+fi
+
+TMP=$(mktemp)
+
+awk -v boundary=$NEXT_SERVER_ID -v number=$NUMBER -f $ROOT_SRC/awk_scripts/destroy_maxscale.awk /etc/maxscale.cnf > $TMP
+cat /etc/maxscale.cnf > /etc/maxscale.cnf.last
+cat $TMP > /etc/maxscale.cnf
+sed -E -i "s/,$MANAGED_SERVERS//g" /etc/maxscale.cnf
+
+systemctl restart maxscale
+
+rm $TMP
diff --git a/terraform-template/docker/Dockerfile b/terraform-template/docker/Dockerfile
@@ -0,0 +1,19 @@
+FROM debian:12-slim
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends \
+ mariadb-server mariadb-client \
+ ca-certificates tzdata \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
+
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+VOLUME ["/var/lib/mysql"]
+
+ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
+CMD ["mariadbd"]
diff --git a/terraform-template/docker/entrypoint.sh b/terraform-template/docker/entrypoint.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+MARIADB_PORT=${MARIADB_PORT:-3311}
+MARIADB_SERVER_ID=${MARIADB_SERVER_ID:-4}
+DATADIR="/var/lib/mysql"
+RUNDIR="/run/mysqld"
+LOCK_FILE=/var/lib/mysql/setup.lock
+
+if ! [ -f "$LOCK_FILE" ]; then
+#Don't do all this in case of an accidental docker restart
+ sed -E -i "s/(server-id=)[0-9]+/\1${MARIADB_SERVER_ID}/" /docker-entrypoint-initdb.d/my.cnf
+ cat /docker-entrypoint-initdb.d/my.cnf > /etc/mysql/my.cnf
+ echo "lock" > $LOCK_FILE
+
+ mkdir -p "$RUNDIR"
+ chown -R mysql:mysql "$RUNDIR"
+ chown -R mysql:mysql "$DATADIR"
+
+ if [ ! -d "${DATADIR}/mysql" ]; then
+ echo "Initializing MariaDB data directory..."
+ mariadb-install-db --user=mysql --datadir="$DATADIR" >/dev/null
+ fi
+
+ #Initialize DB here for importing SQL files, don't allow connections
+ echo "Starting temporary MariaDB (no networking) for init..."
+ mariadbd --user=mysql --datadir="$DATADIR" --skip-networking --socket=/tmp/mysqld.sock &
+ pid="$!"
+
+ #Wait until mariadb server finished initializing before trying to import anything
+ for i in {1..60}; do
+ if mariadb-admin --protocol=socket --socket=/tmp/mysqld.sock ping >/dev/null 2>&1; then
+ break
+ fi
+ sleep 0.5
+ done
+
+ echo "Running init scripts in /docker-entrypoint-initdb.d ..."
+ shopt -s nullglob
+ for f in /docker-entrypoint-initdb.d/*; do
+ case "$f" in
+ *.sql)
+ echo " -> importing $f"
+ mariadb --protocol=socket --socket=/tmp/mysqld.sock < "$f"
+ ;;
+ *.sql.gz)
+ echo " -> importing $f"
+ gunzip -c "$f" | mariadb --protocol=socket --socket=/tmp/mysqld.sock
+ ;;
+ *.sh)
+ echo " -> running $f"
+ bash "$f"
+ ;;
+ *)
+ echo " -> ignoring $f"
+ ;;
+ esac
+ done
+
+ echo "Shutting down temporary MariaDB..."
+ mariadb-admin --protocol=socket --socket=/tmp/mysqld.sock shutdown
+ wait "$pid" || true
+
+fi
+
+echo "Starting MariaDB on port ${MARIADB_PORT}..."
+exec "$@" --user=mysql --datadir="$DATADIR" --bind-address=0.0.0.0 --port="$MARIADB_PORT"
diff --git a/terraform-template/docker/my.cnf b/terraform-template/docker/my.cnf
@@ -0,0 +1,17 @@
+[mysqld]
+server-id=2
+gtid_strict_mode=1
+log_bin=/var/lib/mysql/bin-mariadb.log
+expire_logs_days=8
+sync_binlog=1
+binlog_format = row
+log_slave_updates = 1
+
+[client-server]
+# Port or socket location where to connect
+# port = 3306
+socket = /run/mysqld/mysqld.sock
+
+# Import all .cnf files from configuration directory
+!includedir /etc/mysql/conf.d/
+!includedir /etc/mysql/mariadb.conf.d/
diff --git a/terraform-template/dump/01_queries.sql b/terraform-template/dump/01_queries.sql
@@ -0,0 +1,10 @@
+CHANGE MASTER TO
+MASTER_HOST='192.168.2.37',
+MASTER_PORT=3306,
+MASTER_USER='repl',
+MASTER_PASSWORD=[REDACTED]
+MASTER_USE_GTID=slave_pos;
+
+SET GLOBAL gtid_slave_pos='0-1-61043';
+
+START SLAVE;
diff --git a/terraform-template/init.sh b/terraform-template/init.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+TARGET_SRV=$1
+ROOT_SRC=$2
+REMOTE_DOCKER_DIR=$3
+REMOTE_DOCKER_SCRIPTS_DIR=$4
+DOCKER_BUILD=$5
+DUMP_DIR=$ROOT_SRC/dump
+DOCKER_DIR=$ROOT_SRC/docker
+mkdir -p $DUMP_DIR
+mkdir -p $DOCKER_DIR
+
+if [[ $(ps aux | grep -v grep | grep -c mariadbd) -lt 1 ]]; then
+ echo "Can not find active mariadb instance"
+ exit 1
+fi
+
+sed -E -i "s/(gtid_slave_pos=)'[0-9-]+'/\1'%SLAVE%'/" $DUMP_DIR/01_queries.sql
+
+mysqldump --all-databases --master-data=2 --single-transaction -u root > $DUMP_DIR/00_dump.sql
+mysql -u root --skip-column-names -e "SELECT @@gtid_binlog_pos;" > $DUMP_DIR/gtid_pos.txt
+GTID=$(cat $DUMP_DIR/gtid_pos.txt)
+sed -E -i "s/%SLAVE%/${GTID}/" $DUMP_DIR/01_queries.sql
+# The dump captures a consistent snapshot at a point in time, and the GTID position captured alongside it marks exactly where that snapshot ends. The replication can then pick up from this marker.
+
+scp $DUMP_DIR/00_dump.sql root@$TARGET_SRV:$REMOTE_DOCKER_SCRIPTS_DIR
+scp $DUMP_DIR/01_queries.sql root@$TARGET_SRV:$REMOTE_DOCKER_SCRIPTS_DIR
+
+scp $DOCKER_DIR/Dockerfile root@$TARGET_SRV:$REMOTE_DOCKER_DIR
+scp $DOCKER_DIR/entrypoint.sh root@$TARGET_SRV:$REMOTE_DOCKER_DIR
+
+scp $DOCKER_DIR/my.cnf root@$TARGET_SRV:$REMOTE_DOCKER_SCRIPTS_DIR
+
+ssh root@$TARGET_SRV "docker build -t $DOCKER_BUILD $REMOTE_DOCKER_DIR"
+
+
+
diff --git a/terraform-template/main.tf b/terraform-template/main.tf
@@ -0,0 +1,53 @@
+terraform {
+ required_version = ">= 1.5.0"
+
+ required_providers {
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 3.0"
+ }
+ local = {
+ source = "hashicorp/local"
+ version = "~> 2.0"
+ }
+ }
+}
+
+variable "target_ip" { type = string }
+variable "docker_image" { type = string }
+variable "batch_id" {type = string}
+
+provider "docker" {
+ host = "ssh://root@${var.target_ip}"
+}
+
+resource "docker_network" "lab" {
+ name = "tf-mariadb-lab-net${var.batch_id}"
+}
+
+resource "docker_container" "server" {
+ for_each = local.servers
+
+ name = each.key
+ image = "${var.docker_image}"
+ hostname = each.key
+
+ env = [
+ "MARIADB_PORT=${each.value.port}",
+ "MARIADB_SERVER_ID=${each.value.server_id}",
+ ]
+
+ ports {
+ internal = each.value.port
+ external = each.value.port
+ }
+
+ networks_advanced {
+ name = docker_network.lab.name
+ }
+
+ labels {
+ label = "deployed_by"
+ value = "terraform"
+ }
+}
diff --git a/terraform-template/service_discovery.sh b/terraform-template/service_discovery.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+NUMBER=$1
+PORT=$2
+OPERATION=$3
+ROOT_SRC=$4
+TMP=$(mktemp)
+PORT_CHECK=$(maxctrl list servers | awk -f $ROOT_SRC/awk_scripts/port_check.awk)
+DESIRED_PORT=$(echo "$PORT + 1" | bc)
+
+if [[ $DESIRED_PORT -le $PORT_CHECK && $OPERATION =~ "APPLY" ]]; then
+ echo "Desired port of $DESIRED_PORT is unavailable, failed check against $PORT_CHECK, try setting a higher port!"
+ exit 1
+fi
+
+maxctrl list servers | awk -v number=$NUMBER -v port=$PORT -f $ROOT_SRC/awk_scripts/service_discovery.awk | sed -e 'H;1h;$!d;x;s/\(.*\),/\1/' > $TMP
+
+if ! head -n1 "$TMP" | grep -Eq '^[0-9]+$'; then
+ echo "Failed to derive boundary from maxctrl output" >&2
+ exit 1
+fi
+
+cat $ROOT_SRC/boundary.txt > $ROOT_SRC/boundary.txt.last
+awk '(NR==1){print $0}' $TMP > $ROOT_SRC/boundary.txt
+sed -i '1d' $TMP
+
+cat $ROOT_SRC/locals.tf > $ROOT_SRC/locals.tf.last
+cat $TMP > $ROOT_SRC/locals.tf
+
+rm $TMP