adding edge services

This commit is contained in:
juvdiaz 2026-05-24 10:19:29 -06:00
parent 71fab52e96
commit c7263f4673
10 changed files with 713 additions and 0 deletions

View File

@ -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/<name>` and register them in

View File

@ -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",
]
}

250
bootstrap/edge/main.tf Normal file
View File

@ -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 = [
<<EOT
set -eu
install_dir="${self.triggers.install_dir}"
server_name="${self.triggers.server_name}"
enable_letsencrypt="${self.triggers.enable_letsencrypt}"
letsencrypt_email="${self.triggers.letsencrypt_email}"
letsencrypt_staging="${self.triggers.letsencrypt_staging}"
certbot_image="certbot/certbot:latest"
install_missing_packages() {
missing_packages=""
for package in "$@"; do
if ! dpkg-query -W -f='$${Status}' "$package" 2>/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 <<RENEW_EOT
#!/bin/sh
set -eu
docker run --rm -v "$install_dir/letsencrypt:/etc/letsencrypt" -v "$install_dir/certbot/www:/var/www/certbot" "$certbot_image" renew --webroot -w /var/www/certbot --quiet
if [ -s "$install_dir/letsencrypt/live/$server_name/fullchain.pem" ] &&
[ -s "$install_dir/letsencrypt/live/$server_name/privkey.pem" ]; then
cp -L "$install_dir/letsencrypt/live/$server_name/fullchain.pem" "$install_dir/certs/current.crt"
cp -L "$install_dir/letsencrypt/live/$server_name/privkey.pem" "$install_dir/certs/current.key"
chmod 0644 "$install_dir/certs/current.crt"
chmod 0600 "$install_dir/certs/current.key"
cd "$install_dir"
docker compose exec -T nginx-dev nginx -s reload || docker compose restart nginx-dev
fi
RENEW_EOT
sudo chmod 0755 /usr/local/sbin/homelab-edge-renew-certs.sh
sudo tee /etc/systemd/system/homelab-edge-renew-certs.service >/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
}

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

@ -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:

View File

@ -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}

View File

@ -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

View File

@ -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"
}

28
lab.sh
View File

@ -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."
}