From 9fd085420ab494f8fd492f55324ca98c37bae71f Mon Sep 17 00:00:00 2001 From: Samantha Atkins Date: Fri, 27 Mar 2026 22:15:39 -0400 Subject: [PATCH] Migrate to Proxmox homelab swarm stack definitions Replace old AWS-oriented compose_files with updated service ymls for the 3-node Proxmox cluster (pve, adder, game). Services now target Docker Swarm on the VXLAN overlay (10.10.10.0/24). New services: authentik, ghost (x3), mariadb Updated: postgres, n8n, nats, fusionauth, monerod Added: game node interfaces file for VXLAN config Co-Authored-By: Claude Opus 4.6 (1M context) --- compose_files/Caddyfile | 28 ------ compose_files/files.zip | Bin 4361 -> 0 bytes compose_files/nats.conf | 15 ---- compose_files/nats.yml | 33 ------- compose_files/postgres-init/01-init.sh | 20 ----- compose_files/postgres.yml | 44 --------- proxmox/services/game-interfaces | 51 +++++++++++ services/authentik.yml | 74 +++++++++++++++ {compose_files => services}/caddy.yml | 5 +- {compose_files => services}/fusionauth.yml | 12 ++- services/ghost.yml | 99 +++++++++++++++++++++ services/mariadb.yml | 35 ++++++++ services/monerod-ban-list.txt | 0 services/monerod.yml | 45 ++++++++++ {compose_files => services}/n8n.yml | 32 +++---- services/nats.yml | 37 ++++++++ services/postgres.yml | 38 ++++++++ 17 files changed, 398 insertions(+), 170 deletions(-) delete mode 100644 compose_files/Caddyfile delete mode 100644 compose_files/files.zip delete mode 100644 compose_files/nats.conf delete mode 100644 compose_files/nats.yml delete mode 100755 compose_files/postgres-init/01-init.sh delete mode 100644 compose_files/postgres.yml create mode 100644 proxmox/services/game-interfaces create mode 100644 services/authentik.yml rename {compose_files => services}/caddy.yml (91%) rename {compose_files => services}/fusionauth.yml (74%) create mode 100644 services/ghost.yml create mode 100644 services/mariadb.yml create mode 100644 services/monerod-ban-list.txt create mode 100644 services/monerod.yml rename {compose_files => services}/n8n.yml (52%) create mode 100644 services/nats.yml create mode 100644 services/postgres.yml diff --git a/compose_files/Caddyfile b/compose_files/Caddyfile deleted file mode 100644 index 83f25c1..0000000 --- a/compose_files/Caddyfile +++ /dev/null @@ -1,28 +0,0 @@ -# Caddyfile -# Place this at /etc/caddy/Caddyfile on the caddy instance. -# Caddy will automatically obtain and renew TLS certificates via Let's Encrypt. - -# erda-reader -reader.erdaverse.com { - handle /api/* { - reverse_proxy erda-reader-backend:8000 - } - handle { - reverse_proxy erda-reader-frontend:3000 - } -} - -# FusionAuth -auth.erdaverse.com { - reverse_proxy fusionauth:9011 -} - -# n8n -n8n.erdaverse.com { - reverse_proxy n8n:5678 -} - -# NATS WebSocket -nats.erdaverse.com { - reverse_proxy nats:8080 -} diff --git a/compose_files/files.zip b/compose_files/files.zip deleted file mode 100644 index bf5fb4bb6589173638364edd74c39cf9a6afc8fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4361 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 jJ:9000/if/flow/initial-setup/ + +services: + server: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.2.1} + command: server + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_POSTGRESQL__HOST: postgres_postgres + AUTHENTIK_POSTGRESQL__PORT: 5432 + AUTHENTIK_POSTGRESQL__NAME: authentik_db + AUTHENTIK_POSTGRESQL__USER: authentik_user + AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD} + ports: + - "9000:9000" + - "9443:9443" + volumes: + - authentik_media:/media + - authentik_templates:/templates + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == pve-tools + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + + worker: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.2.1} + command: worker + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} + AUTHENTIK_POSTGRESQL__HOST: postgres_postgres + AUTHENTIK_POSTGRESQL__PORT: 5432 + AUTHENTIK_POSTGRESQL__NAME: authentik_db + AUTHENTIK_POSTGRESQL__USER: authentik_user + AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD} + volumes: + - authentik_media:/media + - authentik_templates:/templates + - authentik_certs:/certs + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == pve-tools + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + +volumes: + authentik_media: + authentik_templates: + authentik_certs: + +networks: + overlay-net: + external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/compose_files/caddy.yml b/services/caddy.yml similarity index 91% rename from compose_files/caddy.yml rename to services/caddy.yml index 7cc62d7..1c0b3d7 100644 --- a/compose_files/caddy.yml +++ b/services/caddy.yml @@ -19,7 +19,7 @@ services: - caddy_data:/data - caddy_config:/config networks: - - erda-net + - overlay-net deploy: replicas: 1 placement: @@ -35,5 +35,6 @@ volumes: caddy_config: networks: - erda-net: + overlay-net: external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/compose_files/fusionauth.yml b/services/fusionauth.yml similarity index 74% rename from compose_files/fusionauth.yml rename to services/fusionauth.yml index d44a921..7bd8c9f 100644 --- a/compose_files/fusionauth.yml +++ b/services/fusionauth.yml @@ -1,14 +1,11 @@ version: '3.8' # Deploy with: -# export POSTGRES_PASSWORD='...' FUSIONAUTH_DB_PASSWORD='...' -# sudo -E docker stack deploy -c fusionauth.yml fusionauth -# -# Passwords sourced from AWS Secrets Manager (swarm_infra_secrets) +# docker stack deploy -c fusionauth.yml fusionauth # # Runs on: CADDY_INSTANCE (ip-10-0-1-168) # FusionAuth is Java-based and memory hungry — deployed on caddy node (t3.large, 8GB) -# Accessible publicly via Caddy reverse proxy at auth.erdaverse.com +# Accessible publicly via Caddy reverse proxy at auth.yourdomain.com services: fusionauth: @@ -23,7 +20,7 @@ services: FUSIONAUTH_APP_RUNTIME_MODE: production SEARCH_TYPE: database networks: - - erda-net + - overlay-net deploy: replicas: 1 placement: @@ -35,5 +32,6 @@ services: max_attempts: 3 networks: - erda-net: + overlay-net: external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/services/ghost.yml b/services/ghost.yml new file mode 100644 index 0000000..4388f94 --- /dev/null +++ b/services/ghost.yml @@ -0,0 +1,99 @@ +version: '3.8' + +# Deploy with: +# docker stack deploy -c ghost.yml ghost +# +# Runs on: adder-ghost +# Three Ghost blog instances, each with its own port and MariaDB database. +# Ghost 1: port 2368, Ghost 2: port 2369, Ghost 3: port 2370 + +services: + ghost1: + image: ghost:5-alpine + environment: + database__client: mysql + database__connection__host: mariadb_mariadb + database__connection__port: 3306 + database__connection__user: ghost1_user + database__connection__password: ${GHOST1_DB_PASSWORD} + database__connection__database: ghost1_db + url: ${GHOST1_URL:-http://localhost:2368} + ports: + - "2368:2368" + volumes: + - ghost1_data:/var/lib/ghost/content + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == adder-ghost + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + + ghost2: + image: ghost:5-alpine + environment: + database__client: mysql + database__connection__host: mariadb_mariadb + database__connection__port: 3306 + database__connection__user: ghost2_user + database__connection__password: ${GHOST2_DB_PASSWORD} + database__connection__database: ghost2_db + url: ${GHOST2_URL:-http://localhost:2369} + server__port: 2369 + ports: + - "2369:2369" + volumes: + - ghost2_data:/var/lib/ghost/content + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == adder-ghost + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + + ghost3: + image: ghost:5-alpine + environment: + database__client: mysql + database__connection__host: mariadb_mariadb + database__connection__port: 3306 + database__connection__user: ghost3_user + database__connection__password: ${GHOST3_DB_PASSWORD} + database__connection__database: ghost3_db + url: ${GHOST3_URL:-http://localhost:2370} + server__port: 2370 + ports: + - "2370:2370" + volumes: + - ghost3_data:/var/lib/ghost/content + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == adder-ghost + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + +volumes: + ghost1_data: + ghost2_data: + ghost3_data: + +networks: + overlay-net: + external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/services/mariadb.yml b/services/mariadb.yml new file mode 100644 index 0000000..0142852 --- /dev/null +++ b/services/mariadb.yml @@ -0,0 +1,35 @@ +version: '3.8' + +# Deploy with: +# docker stack deploy -c mariadb.yml mariadb +# +# Runs on: adder-ghost + +services: + mariadb: + image: mariadb:11 + environment: + MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD} + ports: + - "3306:3306" + volumes: + - mariadb_data:/var/lib/mysql + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == adder-ghost + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + +volumes: + mariadb_data: + +networks: + overlay-net: + external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/services/monerod-ban-list.txt b/services/monerod-ban-list.txt new file mode 100644 index 0000000..e69de29 diff --git a/services/monerod.yml b/services/monerod.yml new file mode 100644 index 0000000..02a1b91 --- /dev/null +++ b/services/monerod.yml @@ -0,0 +1,45 @@ +version: '3.8' + +# Deploy with: +# docker stack deploy -c monerod.yml monerod +# +# Runs on: fedora +# Restricted RPC node with pruning enabled to reduce disk usage. +# Blockchain data is persisted in a named Docker volume. +# Restricted RPC exposed on port 18089 for external wallet access. + +services: + monerod: + image: ghcr.io/sethforprivacy/simple-monerod:latest + command: + - --rpc-restricted-bind-ip=0.0.0.0 + - --rpc-restricted-bind-port=18089 + - --no-igd + - --enable-dns-blocklist + - --ban-list=/home/monero/ban_list.txt + - --prune-blockchain + ports: + - "18080:18080" + - "18089:18089" + volumes: + - bitmonero:/home/monero/.bitmonero + - ./monerod-ban-list.txt:/home/monero/ban_list.txt:ro + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == fedora + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + +volumes: + bitmonero: + +networks: + overlay-net: + external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/compose_files/n8n.yml b/services/n8n.yml similarity index 52% rename from compose_files/n8n.yml rename to services/n8n.yml index faf0b70..e6f96e4 100644 --- a/compose_files/n8n.yml +++ b/services/n8n.yml @@ -3,53 +3,43 @@ version: '3.8' # Deploy with: # docker stack deploy -c n8n.yml n8n # -# Runs on: CADDY_INSTANCE (ip-10-0-1-168) -# Accessible publicly via Caddy reverse proxy at n8n.erdaverse.com +# Runs on: pve-tools services: n8n: image: n8nio/n8n:latest - entrypoint: /bin/sh - command: - - -c - - | - export DB_POSTGRESDB_PASSWORD=$(cat /run/secrets/n8n_db_password) - exec n8n environment: DB_TYPE: postgresdb - DB_POSTGRESDB_HOST: postgres + DB_POSTGRESDB_HOST: postgres_postgres DB_POSTGRESDB_PORT: 5432 DB_POSTGRESDB_DATABASE: n8n_db DB_POSTGRESDB_USER: n8n_user - N8N_HOST: n8n.erdaverse.com + DB_POSTGRESDB_PASSWORD: ${N8N_DB_PASSWORD} + N8N_HOST: n8n.yourdomain.com N8N_PORT: 5678 N8N_PROTOCOL: https - WEBHOOK_URL: https://n8n.erdaverse.com + WEBHOOK_URL: https://n8n.yourdomain.com GENERIC_TIMEZONE: UTC - NODES_EXCLUDE: "[]" - secrets: - - n8n_db_password + # NATS connection for event-driven workflows + # Configure in n8n credentials UI after first boot volumes: - n8n_data:/home/node/.n8n networks: - - erda-net + - overlay-net deploy: replicas: 1 placement: constraints: - - node.hostname == ip-10-0-1-168 + - node.hostname == pve-tools restart_policy: condition: on-failure delay: 5s max_attempts: 3 -secrets: - n8n_db_password: - external: true - volumes: n8n_data: networks: - erda-net: + overlay-net: external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/services/nats.yml b/services/nats.yml new file mode 100644 index 0000000..05e921d --- /dev/null +++ b/services/nats.yml @@ -0,0 +1,37 @@ +version: '3.8' + +# Deploy with: +# docker stack deploy -c nats.yml nats +# +# Runs on: pve-tools +# JetStream enabled for persistent messaging. +# Internal only — services connect to nats_nats:4222 via overlay. + +services: + nats: + image: nats:latest + command: + - -js # Enable JetStream + - -sd=/data # JetStream storage directory + - -m=8222 # Enable monitoring endpoint + volumes: + - nats_data:/data + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == pve-tools + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + +volumes: + nats_data: + +networks: + overlay-net: + external: true + name: ${OVERLAY_NETWORK:-homelab-net} diff --git a/services/postgres.yml b/services/postgres.yml new file mode 100644 index 0000000..3f0b8fa --- /dev/null +++ b/services/postgres.yml @@ -0,0 +1,38 @@ +version: '3.8' + +# Deploy with: +# docker stack deploy -c postgres.yml postgres +# +# Runs on: POSTGRES_INSTANCE (ip-10-0-1-173) +# Creates databases and users for all services on first boot via init scripts. +# Data is persisted in a named Docker volume on the postgres node. + +services: + postgres: + image: postgres:16 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - overlay-net + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == pve-postgres + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 3 + +volumes: + postgres_data: + +networks: + overlay-net: + external: true + name: ${OVERLAY_NETWORK:-homelab-net}