From c7263f4673e05dc2cb5a0bced134f652b2ad2dc4 Mon Sep 17 00:00:00 2001 From: juvdiaz Date: Sun, 24 May 2026 10:19:29 -0600 Subject: [PATCH] adding edge services --- README.md | 30 +++ bootstrap/edge/.terraform.lock.hcl | 39 +++ bootstrap/edge/main.tf | 250 ++++++++++++++++++ bootstrap/edge/templates/default.conf.tftpl | 118 +++++++++ bootstrap/edge/templates/default.vcl.tftpl | 30 +++ .../edge/templates/docker-compose.yml.tftpl | 55 ++++ bootstrap/edge/templates/haproxy.cfg.tftpl | 34 +++ bootstrap/edge/templates/squid.conf.tftpl | 70 +++++ bootstrap/edge/variables.tf | 59 +++++ lab.sh | 28 ++ 10 files changed, 713 insertions(+) create mode 100644 bootstrap/edge/.terraform.lock.hcl create mode 100644 bootstrap/edge/main.tf create mode 100644 bootstrap/edge/templates/default.conf.tftpl create mode 100644 bootstrap/edge/templates/default.vcl.tftpl create mode 100644 bootstrap/edge/templates/docker-compose.yml.tftpl create mode 100644 bootstrap/edge/templates/haproxy.cfg.tftpl create mode 100644 bootstrap/edge/templates/squid.conf.tftpl create mode 100644 bootstrap/edge/variables.tf diff --git a/README.md b/README.md index c798ed0..9d6c969 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ Argo CD. - default apps are `container-registry`, `gitea`, and `website-production` +4. `bootstrap/edge` + - connects to the OCI jump box + - uploads nginx, HAProxy, Varnish, and Squid configs + - obtains and renews Let's Encrypt certificates for the configured hostname + - runs the edge cache/proxy chain with Docker Compose + ## Adding Nodes Add entries to `bootstrap/cluster/variables.tf` or a `.tfvars` file: @@ -70,6 +76,30 @@ single-node rebuild. Add Helm releases through `bootstrap/platform`'s `extra_helm_releases` map. +## Edge Services + +The OCI jump box runs the public edge path: + +```text +nginx -> HAProxy -> Varnish/Squid -> Raspberry Pi Tailscale NodePort +``` + +The `bootstrap/edge` stack renders configs from `bootstrap/edge/templates` and +deploys them to `/opt/homelab-edge` on the OCI host. Defaults are in +`bootstrap/edge/variables.tf`; override them through `TF_VAR_*` or a `.tfvars` +file when the public host, SSH key, server name, backend Tailscale IP, or +NodePort changes. + +Use the configured `server_name` in the browser, for example +`https://lab2025.duckdns.org`. A raw OCI IP address will still show a browser +certificate warning because the trusted certificate is issued for the hostname. + +The edge stack uses HTTP-01 validation, so public DNS for `server_name` must +point to the OCI public IP and inbound TCP 80 and 443 must be open before +`./lab.sh deploy` runs. Set `TF_VAR_letsencrypt_email` to receive expiry notices, +or leave it empty to register without an email. Set +`TF_VAR_enable_letsencrypt=false` to keep using the temporary local certificate. + ## Adding Apps Add Kubernetes manifests under `apps/` and register them in diff --git a/bootstrap/edge/.terraform.lock.hcl b/bootstrap/edge/.terraform.lock.hcl new file mode 100644 index 0000000..02a163c --- /dev/null +++ b/bootstrap/edge/.terraform.lock.hcl @@ -0,0 +1,39 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/null" { + version = "3.3.0" + constraints = "~> 3.2" + hashes = [ + "h1:0r7+t8CqzjfBgHgEiJGBCw+McEUdRXliMdF+Hk29d8o=", + "h1:EvvCOc4FJY3NitSm6BpzCcUPU53LayVCB/tPOxYmy7U=", + "h1:IDVnZXNCh0u4LfeSazc9z1v/kNz+92Eej7ePWV6SbyE=", + "h1:Iw2c0n9/4fS92N5WnJ3CCSwSUXZO953oHp9gj3pWCaM=", + "h1:JofS1og3hPN0ANjH+gNjxrJyyk6znodpC/F0qhp4eEk=", + "h1:QIBhsJ4+5+t0vFEgJwtezNLT31tsptFHOEyGAAhLR1o=", + "h1:RjjoL9qRPwNTwLdtJsYUaFvunbPM2/oujf2DcUcitOE=", + "h1:SSirA+z2VWTs1s+TCAx8vVKg9jh6cRjxqc8LYi2iQTI=", + "h1:U2XZc7hxcpcWp/C2S9LtuGUimhMOD2UT5xAEJJQQQaU=", + "h1:bPG+xE5UonkJv3y/Yn9Q7OfbP2qHU/QKiS31nwfe7S0=", + "h1:eODLdk/pARc4yxChAFtwseVmBr+r5fF9yGOvUhwGEyM=", + "h1:iFj1oM5ZPENspsPqK1kcvZzyP95jJE/CM0rlu0MfIss=", + "h1:mdu+qpyVmjDDLMrcL1JFy+cSyF58I3TFJwB5NssCZ58=", + "h1:tJmep6aoBeDH77XsYU65HAbi0RAjxtsmbCOXmnqT13U=", + "h1:tdMTn1evBLd6KCeLqWdQXCpF07hBu3n5rY6N3rXw3Rc=", + "zh:083dcc0bec53f8abfa3f2aa2ce9d732a9675338fd60ae7d61162e25db7cb08bf", + "zh:19f7456b5a2ad16595860974714bfdb25b87bc16356ea9d5c7453892aaa27864", + "zh:222c0ed1fed4e4c677ebe626104dbfdba66763e264de0d9c27c58ce60104ee69", + "zh:271711d6caa7dd5a4e9b79fe8c679fab61a840bcf80040a0f5ebb425d1b27d97", + "zh:5adcf35f30baaea13f80c2a2c774deb9369892719493049687e23476c9dff40f", + "zh:5bcfd19df16e73d7f0ad75bd09e2b3b86cf6700d09822d585d68304b71de1d97", + "zh:604edecf263e38674decb35bb4e0e048fdc951f26fa103c33065ff9728f0313b", + "zh:782acbfb4fa4807e273e588fe45b4aaea9dd0fd1136f76ec3200f6f4db3af8d6", + "zh:84411a596d528fe67294e5c1cfd0c2036b08802497bcc4215ce518924f3c9a4a", + "zh:85e79eecf3f5348975cffec3016b0eba3baf605646102d4348796ccd2df2e5f6", + "zh:95669535ca17aeefef307ebfd59ce6930953173baae5637e8cbbf0297ec7ad58", + "zh:d04d9b177747bfd66b4a45b5d911a2a7822aa8451f5e35621971fb7a4206b530", + "zh:e6d9c924475283e90833450a14a732f4deb6d9bb131db8f86ab856e894270836", + "zh:ebcab0c8a1334c86ed7cfa53f571a17ad6d27e9901f27a8854ea622a74b54bb6", + "zh:ef9c757bb2c83d2103811a3d86b6ec5be06b0ffc337b84db1582d023bce7cdcd", + ] +} diff --git a/bootstrap/edge/main.tf b/bootstrap/edge/main.tf new file mode 100644 index 0000000..ee9b25c --- /dev/null +++ b/bootstrap/edge/main.tf @@ -0,0 +1,250 @@ +terraform { + required_version = ">= 1.0" + required_providers { + null = { + source = "hashicorp/null" + version = "~> 3.2" + } + } +} + +locals { + compose_file = templatefile("${path.module}/templates/docker-compose.yml.tftpl", {}) + default_conf = templatefile("${path.module}/templates/default.conf.tftpl", { + server_name = var.server_name + }) + default_vcl = templatefile("${path.module}/templates/default.vcl.tftpl", { + backend_host = var.backend_host + backend_port = tostring(var.backend_port) + }) + haproxy_cfg = templatefile("${path.module}/templates/haproxy.cfg.tftpl", { + stats_user = var.haproxy_stats_user + stats_password = var.haproxy_stats_password + }) + squid_conf = templatefile("${path.module}/templates/squid.conf.tftpl", { + backend_host = var.backend_host + backend_port = tostring(var.backend_port) + }) + config_hash = sha256(join("\n---\n", [ + local.compose_file, + local.default_conf, + local.default_vcl, + local.haproxy_cfg, + local.squid_conf, + ])) +} + +resource "null_resource" "edge_services" { + triggers = { + host = var.edge_host + user = var.edge_user + install_dir = var.edge_install_dir + server_name = var.server_name + enable_letsencrypt = tostring(var.enable_letsencrypt) + letsencrypt_email = var.letsencrypt_email + letsencrypt_staging = tostring(var.letsencrypt_staging) + certbot_version = "1" + config_hash = local.config_hash + } + + connection { + type = "ssh" + user = self.triggers.user + private_key = file(var.edge_ssh_key_path) + host = self.triggers.host + } + + provisioner "remote-exec" { + inline = [ + "rm -rf /tmp/homelab-edge", + "mkdir -p /tmp/homelab-edge/config_files", + ] + } + + provisioner "file" { + content = local.compose_file + destination = "/tmp/homelab-edge/docker-compose.yml" + } + + provisioner "file" { + content = local.default_conf + destination = "/tmp/homelab-edge/config_files/default.conf" + } + + provisioner "file" { + content = local.default_vcl + destination = "/tmp/homelab-edge/config_files/default.vcl" + } + + provisioner "file" { + content = local.haproxy_cfg + destination = "/tmp/homelab-edge/config_files/haproxy.cfg" + } + + provisioner "file" { + content = local.squid_conf + destination = "/tmp/homelab-edge/config_files/squid.conf" + } + + provisioner "remote-exec" { + inline = [ + </dev/null | grep -q "install ok installed"; then + missing_packages="$missing_packages $package" + fi + done + if [ -n "$missing_packages" ]; then + sudo apt-get update + sudo apt-get install -y --no-install-recommends $missing_packages + fi +} + +install_missing_packages ca-certificates curl openssl + +if ! command -v docker >/dev/null 2>&1; then + curl -fsSL https://get.docker.com | sudo sh +fi + +if ! sudo docker compose version >/dev/null 2>&1; then + install_missing_packages docker-compose-plugin +fi + +sudo mkdir -p \ + "$install_dir/config_files" \ + "$install_dir/certs" \ + "$install_dir/certbot/www" \ + "$install_dir/letsencrypt" +sudo cp /tmp/homelab-edge/docker-compose.yml "$install_dir/docker-compose.yml" +sudo cp /tmp/homelab-edge/config_files/default.conf "$install_dir/config_files/default.conf" +sudo cp /tmp/homelab-edge/config_files/default.vcl "$install_dir/config_files/default.vcl" +sudo cp /tmp/homelab-edge/config_files/haproxy.cfg "$install_dir/config_files/haproxy.cfg" +sudo cp /tmp/homelab-edge/config_files/squid.conf "$install_dir/config_files/squid.conf" + +if [ ! -s "$install_dir/certs/current.crt" ] || [ ! -s "$install_dir/certs/current.key" ]; then + sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -subj "/CN=$server_name" \ + -keyout "$install_dir/certs/current.key" \ + -out "$install_dir/certs/current.crt" +fi + +deploy_current_certificate() { + if [ ! -s "$install_dir/letsencrypt/live/$server_name/fullchain.pem" ] || + [ ! -s "$install_dir/letsencrypt/live/$server_name/privkey.pem" ]; then + return 1 + fi + + sudo cp -L "$install_dir/letsencrypt/live/$server_name/fullchain.pem" "$install_dir/certs/current.crt" + sudo cp -L "$install_dir/letsencrypt/live/$server_name/privkey.pem" "$install_dir/certs/current.key" + sudo chmod 0644 "$install_dir/certs/current.crt" + sudo chmod 0600 "$install_dir/certs/current.key" +} + +install_renewal_timer() { + sudo tee /usr/local/sbin/homelab-edge-renew-certs.sh >/dev/null </dev/null <<'SERVICE_EOT' +[Unit] +Description=Renew Homelab Edge TLS certificates +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/homelab-edge-renew-certs.sh +SERVICE_EOT + + sudo tee /etc/systemd/system/homelab-edge-renew-certs.timer >/dev/null <<'TIMER_EOT' +[Unit] +Description=Renew Homelab Edge TLS certificates twice daily + +[Timer] +OnCalendar=*-*-* 03,15:17:00 +RandomizedDelaySec=30m +Persistent=true + +[Install] +WantedBy=timers.target +TIMER_EOT + + sudo systemctl daemon-reload + sudo systemctl enable --now homelab-edge-renew-certs.timer >/dev/null +} + +cd "$install_dir" +sudo docker compose pull +sudo docker compose up -d --remove-orphans +sudo docker compose ps + +if [ "$enable_letsencrypt" = "true" ]; then + email_args="--register-unsafely-without-email" + if [ -n "$letsencrypt_email" ]; then + email_args="--email $letsencrypt_email" + fi + + staging_args="" + if [ "$letsencrypt_staging" = "true" ]; then + staging_args="--staging" + fi + + sudo docker run --rm \ + -v "$install_dir/letsencrypt:/etc/letsencrypt" \ + -v "$install_dir/certbot/www:/var/www/certbot" \ + "$certbot_image" certonly \ + --webroot \ + -w /var/www/certbot \ + -d "$server_name" \ + --preferred-challenges http \ + --agree-tos \ + --non-interactive \ + --keep-until-expiring \ + $email_args \ + $staging_args + + deploy_current_certificate + sudo docker compose exec -T nginx-dev nginx -s reload || sudo docker compose restart nginx-dev + install_renewal_timer + curl -fsS --connect-timeout 10 --resolve "$server_name:443:127.0.0.1" "https://$server_name/" >/dev/null +else + sudo systemctl disable --now homelab-edge-renew-certs.timer >/dev/null 2>&1 || true + curl -kfsS --connect-timeout 10 https://127.0.0.1/ >/dev/null +fi +EOT + ] + } +} + +output "edge_host" { + value = var.edge_host +} + +output "edge_install_dir" { + value = var.edge_install_dir +} diff --git a/bootstrap/edge/templates/default.conf.tftpl b/bootstrap/edge/templates/default.conf.tftpl new file mode 100644 index 0000000..cae3299 --- /dev/null +++ b/bootstrap/edge/templates/default.conf.tftpl @@ -0,0 +1,118 @@ +# WAF-like rules +map $request_uri $blocked_uris { + default 0; + ~*(/\.env|/\.git|/\.aws|/wp-admin) 1; +} + +# Rate limiting zone +limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; + +# Cache zones +proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=static_assets:10m max_size=100m inactive=24h; +proxy_cache_path /var/cache/nginx_dynamic levels=1:2 keys_zone=dynamic_content:5m max_size=50m inactive=1h; + +upstream haproxy_backend { + server haproxy-dev:9000; +} + +# Cloudflare IP ranges +set_real_ip_from 173.245.48.0/20; +set_real_ip_from 103.21.244.0/22; +set_real_ip_from 103.22.200.0/22; +set_real_ip_from 103.31.4.0/22; +set_real_ip_from 141.101.64.0/18; +set_real_ip_from 108.162.192.0/18; +set_real_ip_from 190.93.240.0/20; +set_real_ip_from 188.114.96.0/20; +set_real_ip_from 197.234.240.0/22; +set_real_ip_from 198.41.128.0/17; +set_real_ip_from 162.158.0.0/15; +set_real_ip_from 104.16.0.0/13; +set_real_ip_from 104.24.0.0/14; +set_real_ip_from 172.64.0.0/13; +set_real_ip_from 131.0.72.0/22; +real_ip_header CF-Connecting-IP; + +server { + listen 80; + server_name ${server_name}; + + location ^~ /.well-known/acme-challenge/ { + root /var/www/certbot; + default_type "text/plain"; + try_files $uri =404; + } + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name ${server_name}; + + ssl_certificate /etc/nginx/certs/current.crt; + ssl_certificate_key /etc/nginx/certs/current.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + if ($blocked_uris) { return 403; } + + add_header X-Frame-Options SAMEORIGIN; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Content-Type-Options nosniff; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header Permissions-Policy "geolocation=(), microphone=()"; + + gzip on; + gzip_comp_level 2; + gzip_min_length 1000; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + location ~* \.(css|js)$ { + proxy_pass http://haproxy_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; + + proxy_cache static_assets; + proxy_cache_valid 200 301 302 1h; + proxy_cache_key "$scheme$request_method$host$request_uri"; + add_header X-Nginx-Cache "$upstream_cache_status"; + add_header Cache-Control "public, immutable, max-age=604800"; + } + + location ~* \.(jpg|jpeg|png|gif|ico|webp|svg)$ { + proxy_pass http://haproxy_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; + + proxy_cache static_assets; + proxy_cache_valid 200 301 302 1d; + proxy_cache_key "$scheme$request_method$host$request_uri"; + add_header X-Nginx-Cache "$upstream_cache_status"; + add_header Cache-Control "public, immutable, max-age=2592000"; + } + + location / { + limit_req zone=one burst=20 nodelay; + + proxy_pass http://haproxy_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; + + proxy_cache dynamic_content; + proxy_cache_valid 200 301 302 5m; + proxy_cache_key "$scheme$request_method$host$request_uri"; + add_header X-Nginx-Cache "$upstream_cache_status"; + } +} diff --git a/bootstrap/edge/templates/default.vcl.tftpl b/bootstrap/edge/templates/default.vcl.tftpl new file mode 100644 index 0000000..899bbd2 --- /dev/null +++ b/bootstrap/edge/templates/default.vcl.tftpl @@ -0,0 +1,30 @@ +vcl 4.1; + +backend default { + .host = "${backend_host}"; + .port = "${backend_port}"; +} + +sub vcl_recv { + if (req.url == "/" || req.url == "/health") { + return (pass); + } + + if (req.method != "GET" && req.method != "HEAD") { + return (pass); + } + + return (hash); +} + +sub vcl_backend_response { + set beresp.ttl = 1h; +} + +sub vcl_deliver { + if (obj.hits > 0) { + set resp.http.X-Cache = "HIT"; + } else { + set resp.http.X-Cache = "MISS"; + } +} diff --git a/bootstrap/edge/templates/docker-compose.yml.tftpl b/bootstrap/edge/templates/docker-compose.yml.tftpl new file mode 100644 index 0000000..1d41601 --- /dev/null +++ b/bootstrap/edge/templates/docker-compose.yml.tftpl @@ -0,0 +1,55 @@ +services: + nginx-dev: + image: nginx:latest + container_name: nginx-dev + restart: unless-stopped + depends_on: + - haproxy-dev + ports: + - "80:80" + - "443:443" + volumes: + - ./config_files/default.conf:/etc/nginx/conf.d/default.conf:ro + - ./certs:/etc/nginx/certs:ro + - ./certbot/www:/var/www/certbot:ro + - nginx_cache:/var/cache/nginx + - nginx_dynamic_cache:/var/cache/nginx_dynamic + + haproxy-dev: + image: haproxy:alpine + container_name: haproxy-dev + restart: unless-stopped + depends_on: + - varnish-dev + - squid-dev + ports: + - "9000:9000" + - "8404:8404" + volumes: + - ./config_files/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro + + varnish-dev: + image: varnish:fresh-alpine + container_name: varnish-dev + restart: unless-stopped + ports: + - "6081:80" + volumes: + - ./config_files/default.vcl:/etc/varnish/default.vcl:ro + + squid-dev: + image: ubuntu/squid:latest + container_name: squid-dev + restart: unless-stopped + ports: + - "3128:3128" + volumes: + - ./config_files/squid.conf:/etc/squid/squid.conf:ro + - squid_cache:/var/spool/squid + - squid_logs:/var/log/squid + +volumes: + nginx_cache: + nginx_dynamic_cache: + squid_cache: + squid_logs: diff --git a/bootstrap/edge/templates/haproxy.cfg.tftpl b/bootstrap/edge/templates/haproxy.cfg.tftpl new file mode 100644 index 0000000..5a4bbbf --- /dev/null +++ b/bootstrap/edge/templates/haproxy.cfg.tftpl @@ -0,0 +1,34 @@ +global + daemon + maxconn 2000 + log stdout format raw local0 info + +defaults + mode http + option httplog + option dontlognull + timeout connect 5s + timeout client 30s + timeout server 30s + retries 2 + +frontend cache_in + bind *:9000 + mode http + default_backend cache_servers + +backend cache_servers + mode http + balance roundrobin + option tcp-check + server varnish varnish-dev:80 check + server squid squid-dev:3128 check + +listen stats + bind *:8404 + mode http + stats enable + stats uri /haproxy?stats + stats refresh 10s + stats realm Haproxy\ Statistics + stats auth ${stats_user}:${stats_password} diff --git a/bootstrap/edge/templates/squid.conf.tftpl b/bootstrap/edge/templates/squid.conf.tftpl new file mode 100644 index 0000000..5b4cb0f --- /dev/null +++ b/bootstrap/edge/templates/squid.conf.tftpl @@ -0,0 +1,70 @@ +http_port 3128 accel vhost vport defaultsite=${backend_host} +cache_peer ${backend_host} parent ${backend_port} 0 no-query originserver name=myBackend + +acl docker_network src 172.16.0.0/12 +acl localhost src 127.0.0.1/32 +acl to_homelab dst ${backend_host} + +http_access allow docker_network +http_access allow localhost +http_access allow to_homelab +http_access deny all + +cache_peer_access myBackend allow all + +cache_mem 256 MB +maximum_object_size_in_memory 512 KB +maximum_object_size 50 MB +minimum_object_size 0 KB +cache_dir ufs /var/spool/squid 2000 16 256 + +refresh_pattern -i \.html?$ 60 20% 3600 +refresh_pattern -i \.(css|js)$ 1440 10% 10080 +refresh_pattern -i \.(jpg|jpeg|png|gif|ico|webp|svg)$ 10080 5% 43200 +refresh_pattern -i \.(woff|woff2|ttf|eot)$ 43200 1% 31536000 +refresh_pattern -i \.php$ 5 50% 30 +refresh_pattern -i /api/ 5 50% 30 +refresh_pattern . 1440 20% 4320 + +access_log /var/log/squid/access.log squid +cache_log /var/log/squid/cache.log +cache_store_log /var/log/squid/store.log + +request_header_access Allow allow all +request_header_access Authorization allow all +request_header_access WWW-Authenticate allow all +request_header_access Proxy-Authorization allow all +request_header_access Proxy-Authenticate allow all +request_header_access Cache-Control allow all +request_header_access Content-Encoding allow all +request_header_access Content-Length allow all +request_header_access Content-Type allow all +request_header_access Date allow all +request_header_access Expires allow all +request_header_access Host allow all +request_header_access If-Modified-Since allow all +request_header_access Last-Modified allow all +request_header_access Location allow all +request_header_access Pragma allow all +request_header_access Accept allow all +request_header_access Accept-Charset allow all +request_header_access Accept-Encoding allow all +request_header_access Accept-Language allow all +request_header_access Content-Language allow all +request_header_access Mime-Version allow all +request_header_access Retry-After allow all +request_header_access Title allow all +request_header_access Connection allow all +request_header_access Proxy-Connection allow all +request_header_access User-Agent allow all +request_header_access Cookie allow all + +coredump_dir /var/spool/squid + +forwarded_for off +via off + +negative_ttl 5 minutes +positive_dns_ttl 6 hours +read_timeout 15 minutes +client_db off diff --git a/bootstrap/edge/variables.tf b/bootstrap/edge/variables.tf new file mode 100644 index 0000000..7577776 --- /dev/null +++ b/bootstrap/edge/variables.tf @@ -0,0 +1,59 @@ +variable "edge_host" { + type = string + default = "132.145.170.74" +} + +variable "edge_user" { + type = string + default = "ubuntu" +} + +variable "edge_ssh_key_path" { + type = string + default = "/home/jv/.ssh/id_ed25519" +} + +variable "edge_install_dir" { + type = string + default = "/opt/homelab-edge" +} + +variable "server_name" { + type = string + default = "lab2025.duckdns.org" +} + +variable "enable_letsencrypt" { + type = bool + default = true +} + +variable "letsencrypt_email" { + type = string + default = "" +} + +variable "letsencrypt_staging" { + type = bool + default = false +} + +variable "backend_host" { + type = string + default = "100.77.80.72" +} + +variable "backend_port" { + type = number + default = 30080 +} + +variable "haproxy_stats_user" { + type = string + default = "admin" +} + +variable "haproxy_stats_password" { + type = string + default = "adminpassword" +} diff --git a/lab.sh b/lab.sh index 5e58cdb..847968a 100755 --- a/lab.sh +++ b/lab.sh @@ -7,6 +7,25 @@ KUBECONFIG_PATH="${KUBECONFIG_PATH:-${TF_VAR_kubeconfig_path:-/home/jv/.kube/con trap 'rm -f "${BUILDX_CONFIG}"' EXIT +require_debian_server() { + local command_name="$1" + local os_id="" + + if [[ "$(uname -s)" != "Linux" ]]; then + echo "Refusing to run '${command_name}' from this machine. Run it on the Debian homelab server." >&2 + exit 1 + fi + + if [[ -r /etc/os-release ]]; then + os_id="$(awk -F= '$1 == "ID" {gsub(/"/, "", $2); print $2; exit}' /etc/os-release)" + fi + + if [[ "${os_id}" != "debian" ]]; then + echo "Refusing to run '${command_name}' on ${os_id:-unknown OS}. Run it on the Debian homelab server." >&2 + exit 1 + fi +} + run_tofu_stack() { local stack="$1" @@ -222,6 +241,8 @@ refresh_argocd_application() { up() { local registry_endpoint + require_debian_server "up" + registry_endpoint="$(website_registry_endpoint)" export TF_VAR_registry_endpoint="${TF_VAR_registry_endpoint:-${registry_endpoint}}" export TF_VAR_kubeconfig_path="${TF_VAR_kubeconfig_path:-${KUBECONFIG_PATH}}" @@ -278,6 +299,8 @@ EOF recreate_pods_for_selector website-production app=php-website website-production wait_for_deployment_ready website-production php-website-deployment website-production 300 + run_tofu_stack "bootstrap/edge" + echo "Deployment successfully completed." } @@ -286,6 +309,8 @@ nuke() { local worker_targets local target + require_debian_server "nuke" + echo "Brutally nuking the homelab infrastructure..." worker_ssh_targets="${WORKER_SSH_TARGETS-jv@192.168.100.89}" read -r -a worker_targets <<< "${worker_ssh_targets}" @@ -413,6 +438,9 @@ EOF rm -rf "${REPO_ROOT}"/bootstrap/apps/terraform.tfstate* rm -f "${REPO_ROOT}"/bootstrap/apps/.terraform.tfstate.lock.info rm -rf "${REPO_ROOT}"/bootstrap/apps/.terraform/ + rm -rf "${REPO_ROOT}"/bootstrap/edge/terraform.tfstate* + rm -f "${REPO_ROOT}"/bootstrap/edge/.terraform.tfstate.lock.info + rm -rf "${REPO_ROOT}"/bootstrap/edge/.terraform/ echo "Destruction complete. Retained data under /var/openebs/local was left intact." }