diff --git a/README.md b/README.md index 4177df5..f8f4614 100644 --- a/README.md +++ b/README.md @@ -862,9 +862,14 @@ delete/recreate or storage migration steps, and post-restore checks. The older NodePort path is now reserved for special cases such as the local registry. `bootstrap/cluster` still contains `homelab-tailscale-nodeport` support, but app traffic should normally enter through Traefik's MetalLB -address instead of per-app NodePorts. The cluster stack advertises the LAN -subnet from the configured Tailscale worker so the OCI edge can route to the -Traefik LoadBalancer address: +address instead of per-app NodePorts. Before applying the edge stack, Jeannie +configures a Tailscale subnet router so the OCI edge can route to the Traefik +LoadBalancer address. The default route advertiser is the Debian control-plane +host because it can reach both Tailscale and the MetalLB address; set +`LAB_EDGE_TAILSCALE_ROUTER=rpi` to use the Raspberry Pi instead. + +The cluster stack still supports worker-based subnet-route advertisement when +that is useful: ```hcl metallb = { diff --git a/README.md.tmpl b/README.md.tmpl index d705b97..4f1a925 100644 --- a/README.md.tmpl +++ b/README.md.tmpl @@ -862,9 +862,14 @@ delete/recreate or storage migration steps, and post-restore checks. The older NodePort path is now reserved for special cases such as the local registry. `bootstrap/cluster` still contains `homelab-tailscale-nodeport` support, but app traffic should normally enter through Traefik's MetalLB -address instead of per-app NodePorts. The cluster stack advertises the LAN -subnet from the configured Tailscale worker so the OCI edge can route to the -Traefik LoadBalancer address: +address instead of per-app NodePorts. Before applying the edge stack, Jeannie +configures a Tailscale subnet router so the OCI edge can route to the Traefik +LoadBalancer address. The default route advertiser is the Debian control-plane +host because it can reach both Tailscale and the MetalLB address; set +`LAB_EDGE_TAILSCALE_ROUTER=rpi` to use the Raspberry Pi instead. + +The cluster stack still supports worker-based subnet-route advertisement when +that is useful: ```hcl metallb = { diff --git a/docs/jeannie.1.md b/docs/jeannie.1.md index 0634534..0ab4448 100644 --- a/docs/jeannie.1.md +++ b/docs/jeannie.1.md @@ -595,12 +595,16 @@ Raspberry Pi when Docker is missing. Default is to fail. missing. Default is to fail. `LAB_EDGE_CONFIGURE_TAILSCALE_ROUTES` -: Configure the Raspberry Pi as the Tailscale subnet-route advertiser and set -the OCI edge host to accept routes before applying the edge stack. Defaults to -`true`. +: Configure the Tailscale subnet-route advertiser and set the OCI edge host to +accept routes before applying the edge stack. Defaults to `true`. + +`LAB_EDGE_TAILSCALE_ROUTER` +: Host that advertises the edge subnet route. Defaults to `debian`, which is the +control-plane host that can reach the Traefik MetalLB address. Set to `rpi` to +use the Raspberry Pi instead. `LAB_EDGE_TAILSCALE_SUBNET_ROUTE` -: Subnet route advertised by the Raspberry Pi for edge-to-LAN app traffic. +: Subnet route advertised for edge-to-LAN app traffic. Defaults to `LAB_LAN_CIDR`, normally `192.168.100.0/24`. `TF_VAR_haproxy_stats_password` diff --git a/jeannie b/jeannie index 1f82ab2..cddcd0e 100755 --- a/jeannie +++ b/jeannie @@ -4932,6 +4932,7 @@ edge_haproxy_stats_password_valid() { ensure_edge_tailscale_routes() { local enabled="${LAB_EDGE_CONFIGURE_TAILSCALE_ROUTES:-true}" local route="${LAB_EDGE_TAILSCALE_SUBNET_ROUTE:-${LAB_LAN_CIDR:-192.168.100.0/24}}" + local router="${LAB_EDGE_TAILSCALE_ROUTER:-debian}" local rpi_host="${LAB_RPI_HOST:-${LAB_RASPBERRY_HOST:-192.168.100.89}}" local rpi_user="${LAB_RPI_USER:-${LAB_RASPBERRY_USER:-jv}}" local rpi_key="${LAB_RPI_SSH_KEY_PATH:-${LAB_RASPBERRY_SSH_KEY_PATH:-/home/jv/.ssh/id_ed25519}}" @@ -4955,17 +4956,45 @@ ensure_edge_tailscale_routes() { exit 1 fi - echo "Ensuring RPi advertises ${route} and OCI edge accepts Tailscale routes..." - ssh -i "${rpi_key}" -o BatchMode=yes -o ConnectTimeout=10 -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "${rpi_user}@${rpi_host}" "set -eu + case "${router}" in + debian) + echo "Ensuring Debian advertises ${route} and OCI edge accepts Tailscale routes..." + if ! command -v tailscale >/dev/null 2>&1; then + echo "tailscale is not installed on the Debian subnet router." >&2 + exit 1 + fi + sudo mkdir -p /etc/sysctl.d + printf '%s\n' 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-homelab-tailscale-subnet-router.conf >/dev/null + sudo sysctl -w net.ipv4.ip_forward=1 >/dev/null + sudo tailscale set --advertise-routes="${route}" + ;; + rpi | rpi4 | raspberrypi) + echo "Ensuring RPi advertises ${route} and OCI edge accepts Tailscale routes..." + ssh -i "${rpi_key}" -o BatchMode=yes -o ConnectTimeout=10 -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "${rpi_user}@${rpi_host}" "set -eu if ! command -v tailscale >/dev/null 2>&1; then echo 'tailscale is not installed on the RPi subnet router.' >&2 exit 1 fi +sysctl_bin=\"\$(command -v sysctl 2>/dev/null || true)\" +if [ -z \"\$sysctl_bin\" ] && [ -x /usr/sbin/sysctl ]; then + sysctl_bin=/usr/sbin/sysctl +fi +if [ -z \"\$sysctl_bin\" ]; then + echo 'sysctl is not installed on the RPi subnet router.' >&2 + exit 1 +fi sudo mkdir -p /etc/sysctl.d printf '%s\n' 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-homelab-tailscale-subnet-router.conf >/dev/null -sudo sysctl -w net.ipv4.ip_forward=1 >/dev/null +sudo \"\$sysctl_bin\" -w net.ipv4.ip_forward=1 >/dev/null sudo tailscale set --advertise-routes='${route}' " + ;; + *) + echo "LAB_EDGE_TAILSCALE_ROUTER must be debian or rpi." >&2 + exit 1 + ;; + esac + ssh -i "${edge_key}" -o BatchMode=yes -o ConnectTimeout=10 -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "${edge_user}@${edge_host}" "set -eu if ! command -v tailscale >/dev/null 2>&1; then echo 'tailscale is not installed on the OCI edge host.' >&2