From bab789a34795954bbda25d0345c7474a70f6fd78 Mon Sep 17 00:00:00 2001 From: Luke Else Date: Fri, 10 Jul 2026 22:58:56 +0100 Subject: [PATCH] feat: Added automatic script copying functionality --- infra/main.tf | 3 + infra/modules/dev/main.tf | 76 +++++++++++++++++++ infra/modules/dev/outputs.tf | 5 ++ .../runners-docker-compose.yml.tftpl | 7 +- infra/modules/dev/variables.tf | 5 ++ infra/modules/prod/main.tf | 72 ++++++++++++++++++ infra/modules/prod/outputs.tf | 5 ++ infra/modules/prod/variables.tf | 5 ++ infra/modules/vpn/main.tf | 44 +++++++++++ infra/modules/vpn/variables.tf | 5 ++ infra/outputs.tf | 10 +++ infra/versions.tf | 4 + readme.md | 28 +++++-- services/dev/Runners/docker-compose.yml | 7 +- services/dev/gitea-docker-compose.yml | 2 +- services/dev/spinup.sh | 5 ++ services/dev/traefik-docker-compose.yml | 2 +- services/prod/bitwarden-docker-compose.yml | 2 +- services/prod/rd-docker-compose.yml | 4 +- services/prod/spinup.sh | 5 ++ services/prod/status-docker-compose.yml | 2 +- services/prod/traefik-docker-compose.yml | 2 +- services/prod/web-docker-compose.yml | 2 +- 23 files changed, 284 insertions(+), 18 deletions(-) diff --git a/infra/main.tf b/infra/main.tf index da2e66c..328bd6b 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -61,6 +61,7 @@ module "dev" { network_ip_range = var.network_ip_range bootstrap_script = local.bootstrap_script ssh_private_key_path = var.ssh_private_key_path + deploy_user = var.deploy_user runner_count = var.dev_runner_count # module.network.id alone doesn't guarantee the subnet exists yet, and a server @@ -82,6 +83,7 @@ module "prod" { network_ip_range = var.network_ip_range bootstrap_script = local.bootstrap_script ssh_private_key_path = var.ssh_private_key_path + deploy_user = var.deploy_user depends_on = [module.network] } @@ -96,6 +98,7 @@ module "vpn" { allowed_ssh_source_ips = var.allowed_ssh_source_ips bootstrap_script = local.bootstrap_script ssh_private_key_path = var.ssh_private_key_path + deploy_user = var.deploy_user } module "dns" { diff --git a/infra/modules/dev/main.tf b/infra/modules/dev/main.tf index e2b2d25..aed7406 100644 --- a/infra/modules/dev/main.tf +++ b/infra/modules/dev/main.tf @@ -94,6 +94,14 @@ resource "hcloud_volume" "storage" { } } +locals { + # Hetzner's automount convention (hc-utils' 99-hc-volume-automount.rules) is + # always /mnt/HC_Volume_ - deterministic once the volume exists, since the + # id is stable for the volume's lifetime regardless of which server it's + # attached to. + data_dir = "/mnt/HC_Volume_${hcloud_volume.storage.id}" +} + # Renders services/dev/Runners/docker-compose.yml directly into the repo, with # one runner service per var.runner_count. This only touches the local working # tree - deploying the change still goes through the normal scp + spinup.sh flow. @@ -101,6 +109,74 @@ resource "local_file" "runners_compose" { filename = "${path.root}/../services/dev/Runners/docker-compose.yml" content = templatefile("${path.module}/templates/runners-docker-compose.yml.tftpl", { runner_count = var.runner_count + data_dir = local.data_dir }) file_permission = "0644" } + +# DATA_DIR is picked up automatically by `docker compose` (which auto-loads +# .env from its working directory) for every dev/*-docker-compose.yml bind +# mount - see services/dev/*.yml. Not committed (see .gitignore): it's tied to +# the live volume's ID, so it's regenerated by tofu apply, not handed off via git. +resource "local_file" "env" { + filename = "${path.root}/../services/dev/.env" + content = "DATA_DIR=${local.data_dir}\n" + file_permission = "0644" +} + +locals { + # Everything under services/dev except the two files above: those are + # tracked via their own resource content (reading them back with fileset()/ + # filesha1() before they exist would fail during `tofu plan`). + dev_static_files = sort([ + for f in fileset("${path.root}/../services/dev", "**") : + f if f != ".env" && f != "Runners/docker-compose.yml" + ]) + + dev_static_files_hash = sha1(join("", [ + for f in local.dev_static_files : filesha1("${path.root}/../services/dev/${f}") + ])) +} + +# Copies services/dev to the server on every apply that changes it (a hand-edited +# compose file, a new runner count, ...) or recreates the server - not just once +# at first creation. Split from hcloud_server.this because it must run after the +# generated files above, which in turn depend on the volume, which depends on +# the server - so it can't be a provisioner on the server resource itself. +resource "null_resource" "deploy_services" { + triggers = { + server_id = hcloud_server.this.id + static_files = local.dev_static_files_hash + env = local_file.env.content + runners = local_file.runners_compose.content + } + + connection { + type = "ssh" + host = hcloud_server.this.ipv4_address + user = "root" + private_key = file(var.ssh_private_key_path) + } + + provisioner "remote-exec" { + inline = ["mkdir -p /home/${var.deploy_user}/services"] + } + + provisioner "file" { + source = "${path.root}/../services/dev" + destination = "/home/${var.deploy_user}/services" + } + + provisioner "remote-exec" { + inline = [ + "chown -R ${var.deploy_user}:${var.deploy_user} /home/${var.deploy_user}/services", + "chmod +x /home/${var.deploy_user}/services/dev/*.sh", + ] + } + + depends_on = [ + hcloud_server.this, + local_file.env, + local_file.runners_compose, + ] +} diff --git a/infra/modules/dev/outputs.tf b/infra/modules/dev/outputs.tf index 4897389..86e4ee2 100644 --- a/infra/modules/dev/outputs.tf +++ b/infra/modules/dev/outputs.tf @@ -5,3 +5,8 @@ output "ipv4" { output "private_ipv4" { value = var.private_ip } + +output "data_dir" { + description = "Deterministic host path of the auto-mounted volume - see local.data_dir." + value = local.data_dir +} diff --git a/infra/modules/dev/templates/runners-docker-compose.yml.tftpl b/infra/modules/dev/templates/runners-docker-compose.yml.tftpl index 1389842..bb42e52 100644 --- a/infra/modules/dev/templates/runners-docker-compose.yml.tftpl +++ b/infra/modules/dev/templates/runners-docker-compose.yml.tftpl @@ -6,6 +6,11 @@ # GITEA_RUNNER_REGISTRATION_TOKEN is intentionally left as a compose variable # (not baked in here) - services/dev/spinup.sh generates a fresh token and # writes it to Runners/.env immediately before starting these containers. +# +# /data lives on dev's volume (${data_dir}) so runner identity survives a +# server rebuild - baked in directly rather than via .env, since Runners/.env +# is already reserved for the registration token above and gets overwritten +# on every deploy. services: %{ for i in range(runner_count) ~} runner-${i + 1}: @@ -13,7 +18,7 @@ services: container_name: gitea_runner_${i + 1} volumes: - ./config.yaml:/config.yaml - - ./gitea_runner_${i + 1}:/data + - ${data_dir}/gitea_runner_${i + 1}:/data - /var/run/docker.sock:/var/run/docker.sock networks: - proxy diff --git a/infra/modules/dev/variables.tf b/infra/modules/dev/variables.tf index 40a9f0c..49d475e 100644 --- a/infra/modules/dev/variables.tf +++ b/infra/modules/dev/variables.tf @@ -57,3 +57,8 @@ variable "runner_count" { description = "Number of Gitea Actions runner containers to render into services/dev/Runners/docker-compose.yml." type = number } + +variable "deploy_user" { + description = "Non-root sudo user created by the bootstrap script - services/dev is copied into this user's home directory." + type = string +} diff --git a/infra/modules/prod/main.tf b/infra/modules/prod/main.tf index 99fbc09..f3dc6f1 100644 --- a/infra/modules/prod/main.tf +++ b/infra/modules/prod/main.tf @@ -120,3 +120,75 @@ resource "hcloud_volume" "storage" { prevent_destroy = true } } + +locals { + # Hetzner's automount convention (hc-utils' 99-hc-volume-automount.rules) is + # always /mnt/HC_Volume_ - deterministic once the volume exists, since the + # id is stable for the volume's lifetime regardless of which server it's + # attached to. + data_dir = "/mnt/HC_Volume_${hcloud_volume.storage.id}" +} + +# DATA_DIR is picked up automatically by `docker compose` (which auto-loads +# .env from its working directory) for every prod/*-docker-compose.yml bind +# mount - see services/prod/*.yml. Not committed (see .gitignore): it's tied to +# the live volume's ID, so it's regenerated by tofu apply, not handed off via git. +resource "local_file" "env" { + filename = "${path.root}/../services/prod/.env" + content = "DATA_DIR=${local.data_dir}\n" + file_permission = "0644" +} + +locals { + # Everything under services/prod except .env: that's tracked via its own + # resource content (reading it back with fileset()/filesha1() before it + # exists would fail during `tofu plan`). + prod_static_files = sort([ + for f in fileset("${path.root}/../services/prod", "**") : f if f != ".env" + ]) + + prod_static_files_hash = sha1(join("", [ + for f in local.prod_static_files : filesha1("${path.root}/../services/prod/${f}") + ])) +} + +# Copies services/prod to the server on every apply that changes it (a +# hand-edited compose file, ...) or recreates the server - not just once at +# first creation. Split from hcloud_server.this because it must run after +# local_file.env, which in turn depends on the volume, which depends on the +# server - so it can't be a provisioner on the server resource itself. +resource "null_resource" "deploy_services" { + triggers = { + server_id = hcloud_server.this.id + static_files = local.prod_static_files_hash + env = local_file.env.content + } + + connection { + type = "ssh" + host = hcloud_server.this.ipv4_address + user = "root" + private_key = file(var.ssh_private_key_path) + } + + provisioner "remote-exec" { + inline = ["mkdir -p /home/${var.deploy_user}/services"] + } + + provisioner "file" { + source = "${path.root}/../services/prod" + destination = "/home/${var.deploy_user}/services" + } + + provisioner "remote-exec" { + inline = [ + "chown -R ${var.deploy_user}:${var.deploy_user} /home/${var.deploy_user}/services", + "chmod +x /home/${var.deploy_user}/services/prod/*.sh", + ] + } + + depends_on = [ + hcloud_server.this, + local_file.env, + ] +} diff --git a/infra/modules/prod/outputs.tf b/infra/modules/prod/outputs.tf index 4897389..86e4ee2 100644 --- a/infra/modules/prod/outputs.tf +++ b/infra/modules/prod/outputs.tf @@ -5,3 +5,8 @@ output "ipv4" { output "private_ipv4" { value = var.private_ip } + +output "data_dir" { + description = "Deterministic host path of the auto-mounted volume - see local.data_dir." + value = local.data_dir +} diff --git a/infra/modules/prod/variables.tf b/infra/modules/prod/variables.tf index 3eb6544..bab01c1 100644 --- a/infra/modules/prod/variables.tf +++ b/infra/modules/prod/variables.tf @@ -52,3 +52,8 @@ variable "ssh_private_key_path" { description = "Local path to the private key matching one of var.ssh_key_ids, used to run the bootstrap script over SSH." type = string } + +variable "deploy_user" { + description = "Non-root sudo user created by the bootstrap script - services/prod is copied into this user's home directory." + type = string +} diff --git a/infra/modules/vpn/main.tf b/infra/modules/vpn/main.tf index e1d6ffe..b62fb33 100644 --- a/infra/modules/vpn/main.tf +++ b/infra/modules/vpn/main.tf @@ -58,3 +58,47 @@ resource "hcloud_server" "this" { ] } } + +locals { + vpn_static_files = sort(fileset("${path.root}/../services/vpn", "**")) + + vpn_static_files_hash = sha1(join("", [ + for f in local.vpn_static_files : filesha1("${path.root}/../services/vpn/${f}") + ])) +} + +# Copies services/vpn to the server on every apply that changes it (a +# hand-edited compose file, ...) or recreates the server - not just once at +# first creation. Split from hcloud_server.this so it's triggered by content +# changes independently of the bootstrap provisioners above. +resource "null_resource" "deploy_services" { + triggers = { + server_id = hcloud_server.this.id + static_files = local.vpn_static_files_hash + } + + connection { + type = "ssh" + host = hcloud_server.this.ipv4_address + user = "root" + private_key = file(var.ssh_private_key_path) + } + + provisioner "remote-exec" { + inline = ["mkdir -p /home/${var.deploy_user}/services"] + } + + provisioner "file" { + source = "${path.root}/../services/vpn" + destination = "/home/${var.deploy_user}/services" + } + + provisioner "remote-exec" { + inline = [ + "chown -R ${var.deploy_user}:${var.deploy_user} /home/${var.deploy_user}/services", + "chmod +x /home/${var.deploy_user}/services/vpn/*.sh", + ] + } + + depends_on = [hcloud_server.this] +} diff --git a/infra/modules/vpn/variables.tf b/infra/modules/vpn/variables.tf index 1976e9a..384a4ad 100644 --- a/infra/modules/vpn/variables.tf +++ b/infra/modules/vpn/variables.tf @@ -32,3 +32,8 @@ variable "ssh_private_key_path" { description = "Local path to the private key matching one of var.ssh_key_ids, used to run the bootstrap script over SSH." type = string } + +variable "deploy_user" { + description = "Non-root sudo user created by the bootstrap script - services/vpn is copied into this user's home directory." + type = string +} diff --git a/infra/outputs.tf b/infra/outputs.tf index f8e427c..d0b29b1 100644 --- a/infra/outputs.tf +++ b/infra/outputs.tf @@ -14,6 +14,16 @@ output "prod_private_ipv4" { value = module.prod.private_ipv4 } +output "dev_data_dir" { + description = "Deterministic host path of dev's auto-mounted volume - also written to services/dev/.env as DATA_DIR." + value = module.dev.data_dir +} + +output "prod_data_dir" { + description = "Deterministic host path of prod's auto-mounted volume - also written to services/prod/.env as DATA_DIR." + value = module.prod.data_dir +} + output "vpn_ipv4" { value = module.vpn.ipv4 } diff --git a/infra/versions.tf b/infra/versions.tf index 9552a41..303b689 100644 --- a/infra/versions.tf +++ b/infra/versions.tf @@ -10,6 +10,10 @@ terraform { source = "hashicorp/local" version = "~> 2.5" } + null = { + source = "hashicorp/null" + version = "~> 3.2" + } } } diff --git a/readme.md b/readme.md index 4e1883d..f35a312 100644 --- a/readme.md +++ b/readme.md @@ -77,11 +77,11 @@ architecture-beta │ │ └── bootstrap.sh.tftpl # post-install script run on every host right after creation │ └── modules/ │ ├── network/ # shared private network + subnet (used by dev and prod) -│ ├── dev/ # dev server + firewall + volume + renders services/dev/Runners/docker-compose.yml +│ ├── dev/ # dev server + firewall + volume, renders + deploys services/dev/ │ │ └── templates/ │ │ └── runners-docker-compose.yml.tftpl # shape of each generated runner service -│ ├── prod/ # prod server + firewall + volume -│ ├── vpn/ # vpn server + firewall (no private network, no volume) +│ ├── prod/ # prod server + firewall + volume, deploys services/prod/ +│ ├── vpn/ # vpn server + firewall (no private network, no volume), deploys services/vpn/ │ └── dns/ # Hetzner DNS zones + A records for every domain in var.dns_zones ├── services/ # Docker Compose stacks, grouped by which server they run on │ ├── dev/ # Gitea + CI runner + Traefik (git.luke-else.co.uk, cicd.luke-else.co.uk) @@ -124,7 +124,7 @@ This creates, via `module.network` / `module.dev` / `module.prod` / `module.vpn` - one `hcloud_volume` each for `dev` and `prod` (`modules/dev`, `modules/prod`; `vpn` has none) - one Hetzner DNS zone per domain in `var.dns_zones`, plus every A record in [Service inventory](#service-inventory) (`modules/dns` — see [Managing DNS](#managing-dns)) -Useful outputs: `tofu output dev_ipv4`, `tofu output prod_ipv4`, `tofu output vpn_ipv4`, `tofu output dns_nameservers`. +Useful outputs: `tofu output dev_ipv4`, `tofu output prod_ipv4`, `tofu output vpn_ipv4`, `tofu output dns_nameservers`, `tofu output dev_data_dir`, `tofu output prod_data_dir`. `terraform.tfvars` and any `*.tfvars` file are gitignored — never commit real values there. Defaults for server sizes, locations, and IP ranges live in `infra/variables.tf` and are passed down into the modules from `infra/main.tf`; override them per-environment via `terraform.tfvars`. @@ -139,6 +139,16 @@ Immediately after each `hcloud_server` is created, OpenTofu uploads and runs [`i After bootstrap, SSH in as `var.deploy_user` for day-to-day work — root key-based login still works as a fallback. +### Persistent data lives on the volumes, not the server disk + +`dev` and `prod`'s compose files used to bind-mount container data to paths relative to wherever `services//` happened to be copied on the server (e.g. `./gitea:/data`) - i.e. the server's local disk, which doesn't survive the server being destroyed and recreated. `dev-storage` and `prod-storage` (the `hcloud_volume`s) do survive that, so all stateful bind mounts now point there instead: `${DATA_DIR}/gitea:/data`, `${DATA_DIR}/bitwarden/:/data/`, and so on across `services/dev/*.yml` and `services/prod/*.yml`. `vpn` has no volume, so it's untouched - see the architecture table above. + +`DATA_DIR` needing to resolve to the right path is what makes this work, and that's answered deterministically: Hetzner's automount tooling always mounts an auto-mounted volume at `/mnt/HC_Volume_`, and since the volume is a Terraform resource, `modules//main.tf` already knows its `id` the moment it's created - `local.data_dir = "/mnt/HC_Volume_${hcloud_volume.storage.id}"`. Every `tofu apply` writes that value straight into `services/dev/.env` / `services/prod/.env` as `DATA_DIR=...`, which `docker compose` auto-loads for variable substitution in any compose file run from that directory (the same mechanism the Gitea runner token already uses). Query it directly with `tofu output dev_data_dir` / `tofu output prod_data_dir`. + +These `.env` files are deliberately not committed (see `.gitignore`): the path is tied to the *live* volume's ID, so it's regenerated by `tofu apply`, not handed off via git. Run `tofu apply` at least once before the first deploy, and again if a volume is ever destroyed and recreated (new ID); `spinup.sh` on both hosts refuses to start anything if `.env` is missing, rather than silently falling back to a relative path. + +The Gitea Actions runners are the one exception to the `.env` mechanism: their `/data` path is baked directly into the generated `Runners/docker-compose.yml` at render time (via the same `templatefile()` call as `dev_runner_count`), because `Runners/.env` is already reserved for the registration token and gets overwritten on every deploy. + ### Scaling Gitea Actions runners The number of Gitea Actions runner containers on `dev` is an OpenTofu input, `var.dev_runner_count` (default `3`). On every `tofu apply`, `modules/dev`'s `local_file.runners_compose` resource renders [`modules/dev/templates/runners-docker-compose.yml.tftpl`](infra/modules/dev/templates/runners-docker-compose.yml.tftpl) straight into [`services/dev/Runners/docker-compose.yml`](services/dev/Runners/docker-compose.yml) in your working tree — one `runner-N` service per count, each with its own container name and `/data` volume so their registrations don't collide. That file is generated: change `dev_runner_count` in `terraform.tfvars` and re-run `tofu apply` rather than hand-editing it. @@ -151,9 +161,9 @@ Registration tokens are handled automatically, not baked into the generated file `dev` and `prod`'s firewalls only accept SSH from `vpn`'s public IP (see [Security notes](#security-notes)), but `vpn`'s own OpenVPN service isn't running until you deploy it — so on a from-scratch `tofu apply`, the `dev`/`prod` bootstrap provisioners can't connect yet. Bring the estate up in this order: -1. `tofu apply -target=module.vpn` — creates `vpn` only; its firewall still allows SSH from `var.allowed_ssh_source_ips`, so its own bootstrap provisioner runs fine. -2. Deploy and start the VPN service on `vpn` (see [Deploying the services](#deploying-the-services-services)), then connect to it with an OpenVPN client. -3. `tofu apply` — creates `network`, `dev`, `prod`. Now that your machine is tunneled through `vpn`, its NATed egress IP matches the firewall rule and the `dev`/`prod` bootstrap provisioners can connect. +1. `tofu apply -target=module.vpn` — creates `vpn` only; its firewall still allows SSH from `var.allowed_ssh_source_ips`, so its bootstrap provisioner and `null_resource.deploy_services` (which copies `services/vpn` over) both run fine. +2. SSH in as `var.deploy_user` and run `~/services/vpn/spinup.sh` to start the VPN service, then connect to it with an OpenVPN client. +3. `tofu apply` — creates `network`, `dev`, `prod`. Now that your machine is tunneled through `vpn`, its NATed egress IP matches the firewall rule and the `dev`/`prod` bootstrap + deploy provisioners can connect. If step 3 is run before you're connected to the VPN, the `dev`/`prod` provisioners will fail and Terraform will mark those servers tainted — re-running `tofu apply` once connected will recreate and re-bootstrap them. @@ -169,7 +179,7 @@ Only A records for IPv4 are managed here — none of the modules currently track ## Deploying the services (`services/`) -Once a server exists, copy the relevant `services//` directory to it (e.g. `scp -r services/prod user@:~/services`) and run the matching script from inside that directory: +`tofu apply` already puts `services//` on the matching server for you — a `null_resource.deploy_services` in each `modules//main.tf` copies it to `/home//services/` and `chown`s it to `var.deploy_user`, re-running whenever the server is recreated or the directory's contents change (a hand-edited compose file, a new `dev_runner_count`, a refreshed `DATA_DIR`, ...) - not just once at first creation. So after applying, just SSH in as `var.deploy_user` and run the matching script from inside that directory: ```sh # on dev @@ -185,6 +195,8 @@ Once a server exists, copy the relevant `services//` directory to it (e.g. ./spindown.sh ``` +If you ever need to force a re-sync without going through Terraform (e.g. testing a local edit before committing it), a manual `scp -r services/prod deploy@:~/services` still works fine - `tofu apply` will just overwrite it again next time triggers change. + Each stack can also be managed individually with plain Compose, e.g.: ```sh diff --git a/services/dev/Runners/docker-compose.yml b/services/dev/Runners/docker-compose.yml index 51f84a7..6d0f9d4 100644 --- a/services/dev/Runners/docker-compose.yml +++ b/services/dev/Runners/docker-compose.yml @@ -6,13 +6,18 @@ # GITEA_RUNNER_REGISTRATION_TOKEN is intentionally left as a compose variable # (not baked in here) - services/dev/spinup.sh generates a fresh token and # writes it to Runners/.env immediately before starting these containers. +# +# /data lives on dev's volume (/mnt/HC_Volume_PENDING_TOFU_APPLY) so runner +# identity survives a server rebuild - baked in directly rather than via .env, +# since Runners/.env is already reserved for the registration token above and +# gets overwritten on every deploy. services: runner-1: image: gitea/act_runner:latest container_name: gitea_runner_1 volumes: - ./config.yaml:/config.yaml - - ./gitea_runner_1:/data + - /mnt/HC_Volume_PENDING_TOFU_APPLY/gitea_runner_1:/data - /var/run/docker.sock:/var/run/docker.sock networks: - proxy diff --git a/services/dev/gitea-docker-compose.yml b/services/dev/gitea-docker-compose.yml index 3724a79..4354a68 100644 --- a/services/dev/gitea-docker-compose.yml +++ b/services/dev/gitea-docker-compose.yml @@ -4,7 +4,7 @@ services: image: gitea/gitea:latest container_name: gitea volumes: - - ./gitea:/data + - ${DATA_DIR}/gitea:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro networks: diff --git a/services/dev/spinup.sh b/services/dev/spinup.sh index 1aa5a7c..0b4e466 100755 --- a/services/dev/spinup.sh +++ b/services/dev/spinup.sh @@ -3,6 +3,11 @@ set -e cd "$(dirname "$0")" +if [ ! -f .env ]; then + echo "Missing .env (expected DATA_DIR=/mnt/HC_Volume_...) - run 'tofu apply' in infra/ first, then re-copy this directory." >&2 + exit 1 +fi + docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d docker compose -f gitea-docker-compose.yml pull && docker compose -f gitea-docker-compose.yml up -d diff --git a/services/dev/traefik-docker-compose.yml b/services/dev/traefik-docker-compose.yml index 41f308c..0a69b43 100644 --- a/services/dev/traefik-docker-compose.yml +++ b/services/dev/traefik-docker-compose.yml @@ -15,7 +15,7 @@ services: - "80:80" - "443:443" volumes: - - "./letsencrypt:/letsencrypt" + - "${DATA_DIR}/letsencrypt:/letsencrypt" - "/var/run/docker.sock:/var/run/docker.sock:ro" networks: - proxy diff --git a/services/prod/bitwarden-docker-compose.yml b/services/prod/bitwarden-docker-compose.yml index 91c1ffe..8128bda 100644 --- a/services/prod/bitwarden-docker-compose.yml +++ b/services/prod/bitwarden-docker-compose.yml @@ -5,7 +5,7 @@ services: image: "vaultwarden/server:latest" container_name: vaultwarden volumes: - - ./bitwarden/:/data/ + - ${DATA_DIR}/bitwarden/:/data/ networks: - proxy labels: diff --git a/services/prod/rd-docker-compose.yml b/services/prod/rd-docker-compose.yml index 4dc4145..bab3865 100644 --- a/services/prod/rd-docker-compose.yml +++ b/services/prod/rd-docker-compose.yml @@ -6,7 +6,7 @@ services: restart: unless-stopped volumes: - - ./data:/root + - ${DATA_DIR}/rustdesk:/root networks: - proxy @@ -39,7 +39,7 @@ services: restart: unless-stopped volumes: - - ./data:/root + - ${DATA_DIR}/rustdesk:/root networks: - proxy diff --git a/services/prod/spinup.sh b/services/prod/spinup.sh index 8fa967e..d1e874e 100755 --- a/services/prod/spinup.sh +++ b/services/prod/spinup.sh @@ -3,6 +3,11 @@ set -e cd "$(dirname "$0")" +if [ ! -f .env ]; then + echo "Missing .env (expected DATA_DIR=/mnt/HC_Volume_...) - run 'tofu apply' in infra/ first, then re-copy this directory." >&2 + exit 1 +fi + docker compose -f traefik-docker-compose.yml pull && docker compose -f traefik-docker-compose.yml up -d sleep 20 # Allow Traefik + registry auth to settle before starting the rest of the services diff --git a/services/prod/status-docker-compose.yml b/services/prod/status-docker-compose.yml index 0e833c2..9595387 100644 --- a/services/prod/status-docker-compose.yml +++ b/services/prod/status-docker-compose.yml @@ -3,7 +3,7 @@ services: image: louislam/uptime-kuma:latest container_name: status volumes: - - ./uptime-kuma/data:/app/data + - ${DATA_DIR}/uptime-kuma/data:/app/data networks: - proxy labels: diff --git a/services/prod/traefik-docker-compose.yml b/services/prod/traefik-docker-compose.yml index 109a7eb..a4ed496 100644 --- a/services/prod/traefik-docker-compose.yml +++ b/services/prod/traefik-docker-compose.yml @@ -20,7 +20,7 @@ services: - "443:443" - "27017:27017" volumes: - - "./letsencrypt:/letsencrypt" + - "${DATA_DIR}/letsencrypt:/letsencrypt" - "/var/run/docker.sock:/var/run/docker.sock:ro" networks: - proxy diff --git a/services/prod/web-docker-compose.yml b/services/prod/web-docker-compose.yml index ffc2c6b..af5adac 100644 --- a/services/prod/web-docker-compose.yml +++ b/services/prod/web-docker-compose.yml @@ -81,7 +81,7 @@ services: image: "php:apache" container_name: snexo volumes: - - ./snexo.co.uk/:/var/www/html + - ${DATA_DIR}/snexo.co.uk/:/var/www/html networks: - proxy labels: