Tunnel argocd/ha/longhorn/lingo/blog/serpbear through Hetzner

Refactor node001 nginx to drive both the SNI map and the port-80 vhosts
from a single tunneledHosts list so adding a service is one line.
This commit is contained in:
Ruben Hensen
2026-05-19 23:14:04 +02:00
parent 8d383d4261
commit ffe8830b5e
2 changed files with 74 additions and 97 deletions
+6 -6
View File
@@ -26,7 +26,7 @@ records:
- name: "argocd" - name: "argocd"
expire: 300 expire: 300
type: A type: A
content: "62.41.87.114" content: "46.224.26.65"
- name: "vault" - name: "vault"
expire: 300 expire: 300
type: A type: A
@@ -38,23 +38,23 @@ records:
- name: "ha" - name: "ha"
expire: 300 expire: 300
type: A type: A
content: "62.41.87.114" content: "46.224.26.65"
- name: "longhorn" - name: "longhorn"
expire: 300 expire: 300
type: A type: A
content: "62.41.87.114" content: "46.224.26.65"
- name: "lingo" - name: "lingo"
expire: 300 expire: 300
type: A type: A
content: "62.41.87.114" content: "46.224.26.65"
- name: "blog" - name: "blog"
expire: 300 expire: 300
type: A type: A
content: "62.41.87.114" content: "46.224.26.65"
- name: "serpbear" - name: "serpbear"
expire: 300 expire: 300
type: A type: A
content: "62.41.87.114" content: "46.224.26.65"
- name: "@" - name: "@"
expire: 300 expire: 300
type: MX type: MX
+51 -74
View File
@@ -3,6 +3,39 @@ let
# Public IP of the home network where the k8s cluster ingress lives. # Public IP of the home network where the k8s cluster ingress lives.
# Keep in sync with dns/domains/rubenhensen.nl.yaml. # Keep in sync with dns/domains/rubenhensen.nl.yaml.
homeIP = "62.41.87.114"; homeIP = "62.41.87.114";
# Hosts tunneled to the home k8s cluster. Each entry gets:
# * an SNI map entry for TCP passthrough on :443
# * an HTTP vhost on :80 that reverse-proxies to the cluster
# The cluster's nginx-ingress terminates TLS with cert-manager.
tunneledHosts = [
"rss.rubenhensen.nl"
"authentik.rubenhensen.nl"
"vault.rubenhensen.nl"
"ynab.rubenhensen.nl"
"argocd.rubenhensen.nl"
"ha.rubenhensen.nl"
"longhorn.rubenhensen.nl"
"lingo.rubenhensen.nl"
"blog.rubenhensen.nl"
"serpbear.rubenhensen.nl"
];
sniMapEntries =
lib.concatMapStringsSep "\n"
(h: " ${h} ${homeIP}:443;")
tunneledHosts;
tunneledVhosts = lib.listToAttrs (map (h: {
name = h;
value = {
listen = [
{ addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; }
];
locations."/".proxyPass = "http://${homeIP}";
};
}) tunneledHosts);
in in
{ {
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
@@ -53,10 +86,7 @@ in
# - tunneled hosts → home cluster ingress on 443 # - tunneled hosts → home cluster ingress on 443
streamConfig = '' streamConfig = ''
map $ssl_preread_server_name $tunnel_upstream { map $ssl_preread_server_name $tunnel_upstream {
rss.rubenhensen.nl ${homeIP}:443; ${sniMapEntries}
authentik.rubenhensen.nl ${homeIP}:443;
vault.rubenhensen.nl ${homeIP}:443;
ynab.rubenhensen.nl ${homeIP}:443;
default 127.0.0.1:8443; default 127.0.0.1:8443;
} }
@@ -68,95 +98,42 @@ in
} }
''; '';
# Tunneled hosts: forward plain HTTP to the home cluster so the # Port 80: tunneled hosts reverse-proxy to the home cluster so its
# cluster's nginx-ingress handles HTTP→HTTPS redirects and # nginx-ingress handles HTTP→HTTPS redirects and cert-manager
# cert-manager HTTP-01 ACME challenges. # HTTP-01 ACME challenges. Mail-related hosts serve ACME challenges
virtualHosts."rss.rubenhensen.nl" = { # locally for stalwart's cert and redirect everything else to HTTPS.
virtualHosts = tunneledVhosts // {
"mail.rubenhensen.nl" = {
listen = [ listen = [
{ addr = "0.0.0.0"; port = 80; } { addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; } { addr = "[::]"; port = 80; }
]; ];
locations."/" = { locations."/.well-known/acme-challenge/".root = "/var/lib/acme/acme-challenge";
proxyPass = "http://${homeIP}"; locations."/".return = "301 https://$host$request_uri";
}; };
}; "autoconfig.rubenhensen.nl" = {
virtualHosts."authentik.rubenhensen.nl" = {
listen = [ listen = [
{ addr = "0.0.0.0"; port = 80; } { addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; } { addr = "[::]"; port = 80; }
]; ];
locations."/" = { locations."/.well-known/acme-challenge/".root = "/var/lib/acme/acme-challenge";
proxyPass = "http://${homeIP}"; locations."/".return = "301 https://$host$request_uri";
}; };
}; "autodiscover.rubenhensen.nl" = {
virtualHosts."vault.rubenhensen.nl" = {
listen = [ listen = [
{ addr = "0.0.0.0"; port = 80; } { addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; } { addr = "[::]"; port = 80; }
]; ];
locations."/" = { locations."/.well-known/acme-challenge/".root = "/var/lib/acme/acme-challenge";
proxyPass = "http://${homeIP}"; locations."/".return = "301 https://$host$request_uri";
}; };
}; "rubenhensen.nl" = {
virtualHosts."ynab.rubenhensen.nl" = {
listen = [ listen = [
{ addr = "0.0.0.0"; port = 80; } { addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; } { addr = "[::]"; port = 80; }
]; ];
locations."/" = { locations."/.well-known/acme-challenge/".root = "/var/lib/acme/acme-challenge";
proxyPass = "http://${homeIP}"; locations."/".return = "301 https://$host$request_uri";
};
};
virtualHosts."mail.rubenhensen.nl" = {
listen = [
{ addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; }
];
locations."/.well-known/acme-challenge/" = {
root = "/var/lib/acme/acme-challenge";
};
locations."/" = {
return = "301 https://$host$request_uri";
};
};
virtualHosts."autoconfig.rubenhensen.nl" = {
listen = [
{ addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; }
];
locations."/.well-known/acme-challenge/" = {
root = "/var/lib/acme/acme-challenge";
};
locations."/" = {
return = "301 https://$host$request_uri";
};
};
virtualHosts."autodiscover.rubenhensen.nl" = {
listen = [
{ addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; }
];
locations."/.well-known/acme-challenge/" = {
root = "/var/lib/acme/acme-challenge";
};
locations."/" = {
return = "301 https://$host$request_uri";
};
};
virtualHosts."rubenhensen.nl" = {
listen = [
{ addr = "0.0.0.0"; port = 80; }
{ addr = "[::]"; port = 80; }
];
locations."/.well-known/acme-challenge/" = {
root = "/var/lib/acme/acme-challenge";
};
locations."/" = {
return = "301 https://$host$request_uri";
}; };
}; };
}; };