feat(homelab): setup route & chain generation

This commit is contained in:
2025-12-30 14:08:27 -08:00
parent f89a7e1813
commit f54c95980f
13 changed files with 319 additions and 402 deletions

View File

@@ -7,9 +7,22 @@ routes = [
port = 3000, port = 3000,
private = false private = false
}, },
{
kind = "TCP",
name = "gitea-ssh",
namespace = "git",
service = "gitea-ssh",
port = 22,
private = false
},
{
name = "home-assistant",
namespace = "home",
port = 8123,
private = true
},
{ {
name = "sonarr", name = "sonarr",
hostname = "sonarr",
namespace = "media", namespace = "media",
port = 8787, port = 8787,
private = true private = true
@@ -24,10 +37,51 @@ routes = [
}, },
{ {
name = "pihole", name = "pihole",
hostname = "pihole",
namespace = "pihole-system", namespace = "pihole-system",
service = "pihole-web", service = "pihole-web",
port = 80, port = 80,
private = true private = true
} },
{
kind = "TCP",
name = "minecraft-router",
hostname = "minecraft",
namespace = "minecraft",
service = "minecraft-router-mc-router",
port = 25565,
private = false
},
{
name = "prowlarr",
namespace = "media",
port = 9696,
private = true
},
{
name = "radarr",
namespace = "media",
port = 7878,
private = true
},
{
name = "qbittorrent",
hostname = "qbit",
namespace = "media",
port = 8090,
private = true
},
{
name = "flaresolverr",
hostname = "flare",
namespace = "media",
port = 8191,
private = true
},
{
name = "jellyfin",
hostname = "media",
namespace = "media",
port = 8096,
private = true
},
] ]

View File

@@ -7,13 +7,5 @@ resources:
- ./traefik/private-networks.yaml - ./traefik/private-networks.yaml
- ./traefik/chains.yaml - ./traefik/chains.yaml
- ./cert-manager/config.yaml - ./cert-manager/config.yaml
- ./routes/minecraft.yaml - ./routes.yaml
- ./routes/gitea/ssh.yaml
- ./routes/gitea/http.yaml
- ./routes/longhorn.yaml
- ./routes/home-assistant.yaml
- ./routes/consul-media.yaml
- ./routes/consul-vaultwarden.yaml
- ./routes/pihole.yaml
- ./routes/media/sonarr.yaml
- ./media/sonarr.yaml - ./media/sonarr.yaml

View File

@@ -14,6 +14,42 @@ spec:
- name: gitea-http - name: gitea-http
port: 3000 port: 3000
--- ---
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitea-ssh
namespace: git
spec:
entryPoints:
- gitea-ssh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
port: 22
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: home-assistant
namespace: home
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- home-assistant.lucalise.ca
rules:
- backendRefs:
- name: home-assistant
port: 8123
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
---
apiVersion: gateway.networking.k8s.io/v1 apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute kind: HTTPRoute
metadata: metadata:
@@ -73,6 +109,130 @@ spec:
- backendRefs: - backendRefs:
- name: pihole-web - name: pihole-web
port: 80 port: 80
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
---
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: minecraft-router
namespace: minecraft
spec:
entryPoints:
- minecraft-router
routes:
- match: HostSNI(`*`)
services:
- name: minecraft-router-mc-router
port: 25565
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: prowlarr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- prowlarr.lucalise.ca
rules:
- backendRefs:
- name: prowlarr
port: 9696
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: radarr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- radarr.lucalise.ca
rules:
- backendRefs:
- name: radarr
port: 7878
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: qbittorrent
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- qbit.lucalise.ca
rules:
- backendRefs:
- name: qbittorrent
port: 8090
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: flaresolverr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- flare.lucalise.ca
rules:
- backendRefs:
- name: flaresolverr
port: 8191
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: jellyfin
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- media.lucalise.ca
rules:
- backendRefs:
- name: jellyfin
port: 8096
filters: filters:
- type: ExtensionRef - type: ExtensionRef
extensionRef: extensionRef:

View File

@@ -1,233 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: bazarr
namespace: media
spec:
ports:
- port: 6767
targetPort: 6767
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: bazarr
namespace: media
subsets:
- addresses:
- ip: 192.168.20.20
ports:
- port: 6767
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: bazarr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "bazarr.lucalise.ca"
rules:
- backendRefs:
- name: bazarr
port: 6767
---
apiVersion: v1
kind: Service
metadata:
name: prowlarr
namespace: media
spec:
ports:
- port: 9696
targetPort: 9696
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: prowlarr
namespace: media
subsets:
- addresses:
- ip: 192.168.20.17
ports:
- port: 9696
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: prowlarr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "prowlarr.lucalise.ca"
rules:
- backendRefs:
- name: prowlarr
port: 9696
---
apiVersion: v1
kind: Service
metadata:
name: radarr
namespace: media
spec:
ports:
- port: 7878
targetPort: 7878
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: radarr
namespace: media
subsets:
- addresses:
- ip: 192.168.20.15
ports:
- port: 7878
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: radarr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "radarr.lucalise.ca"
rules:
- backendRefs:
- name: radarr
port: 7878
---
apiVersion: v1
kind: Service
metadata:
name: qbittorrent
namespace: media
spec:
ports:
- port: 8090
targetPort: 8090
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: qbittorrent
namespace: media
subsets:
- addresses:
- ip: 192.168.20.6
ports:
- port: 8090
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: qbittorrent
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "qbit.lucalise.ca"
rules:
- backendRefs:
- name: qbittorrent
port: 8090
---
apiVersion: v1
kind: Service
metadata:
name: flaresolverr
namespace: media
spec:
ports:
- port: 8191
targetPort: 8191
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: flaresolverr
namespace: media
subsets:
- addresses:
- ip: 192.168.20.4
ports:
- port: 8191
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: flaresolverr
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "flare.lucalise.ca"
rules:
- backendRefs:
- name: flaresolverr
port: 8191
---
apiVersion: v1
kind: Service
metadata:
name: jellyfin
namespace: media
spec:
ports:
- port: 8096
targetPort: 8096
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: jellyfin
namespace: media
subsets:
- addresses:
- ip: 192.168.20.2
ports:
- port: 8096
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: jellyfin
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "media.lucalise.ca"
rules:
- backendRefs:
- name: jellyfin
port: 8096

View File

@@ -1,43 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: vaultwarden
---
apiVersion: v1
kind: Service
metadata:
name: vaultwarden
namespace: vaultwarden
spec:
ports:
- port: 8000
targetPort: 8000
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: vaultwarden
namespace: vaultwarden
subsets:
- addresses:
- ip: 192.168.20.22
ports:
- port: 8000
protocol: TCP
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: vaultwarden
namespace: vaultwarden
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "vault.lucalise.ca"
rules:
- backendRefs:
- name: vaultwarden
port: 8000

View File

@@ -1,15 +0,0 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitea
namespace: git
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "git.lucalise.ca"
rules:
- backendRefs:
- name: gitea-http
port: 3000

View File

@@ -1,13 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitea-ssh
namespace: git
spec:
entryPoints:
- ssh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
port: 22

View File

@@ -1,21 +0,0 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: home-assistant
namespace: media
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "home-assistant.lucalise.ca"
rules:
- filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
backendRefs:
- name: home-assistant
port: 8123

View File

@@ -1,21 +0,0 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: longhorn
namespace: longhorn-system
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "storage.lucalise.ca"
rules:
- filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
backendRefs:
- name: longhorn-frontend
port: 80

View File

@@ -1,13 +0,0 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: minecraft-router
namespace: minecraft
spec:
entryPoints:
- minecraft
routes:
- match: HostSNI(`*`)
services:
- name: minecraft-router-mc-router
port: 25565

View File

@@ -1,21 +0,0 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: pihole
namespace: pihole-system
spec:
parentRefs:
- name: traefik-gateway
namespace: kube-system
hostnames:
- "pihole.lucalise.ca"
rules:
- filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: private-networks
backendRefs:
- name: pihole-web
port: 80

View File

@@ -24,7 +24,7 @@ apiVersion: traefik.io/v1alpha1
kind: Middleware kind: Middleware
metadata: metadata:
name: private-networks name: private-networks
namespace: pihole-system namespace: media
spec: spec:
chain: chain:
middlewares: middlewares:
@@ -35,9 +35,9 @@ apiVersion: traefik.io/v1alpha1
kind: Middleware kind: Middleware
metadata: metadata:
name: private-networks name: private-networks
namespace: media namespace: pihole-system
spec: spec:
chain: chain:
middlewares: middlewares:
- name: private-networks - name: private-networks
namespace: kube-system namespace: kube-system

View File

@@ -1,19 +1,31 @@
use std::collections::BTreeSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{Config, HelperError}; use crate::{Config, HelperError};
#[derive(Serialize, Deserialize, Default)] #[derive(Serialize, Deserialize, Default)]
pub struct Route { pub struct Route {
#[serde(default)]
kind: RouteKind, kind: RouteKind,
name: String, name: String,
hostname: String, hostname: Option<String>,
entrypoint: Option<String>,
namespace: String, namespace: String,
service: Option<String>, service: Option<String>,
port: i16, port: i16,
private: bool, private: bool,
} }
impl Route {
fn hostname(&self) -> &str {
self.hostname.as_ref().unwrap_or(&self.name)
}
}
#[derive(Serialize, Deserialize, Default)]
enum RouteKind { enum RouteKind {
#[default]
HTTP, HTTP,
TCP, TCP,
} }
@@ -30,17 +42,62 @@ pub fn generate_routes(config: &Config) -> Result<(), HelperError> {
acc.push_str(&generate_route(r)); acc.push_str(&generate_route(r));
acc acc
}); });
let chains = generate_chains(&config.routes);
std::fs::write("kustomize/routes.yaml", &routes)?; std::fs::write("kustomize/routes.yaml", &routes)?;
std::fs::write("kustomize/traefik/chains.yaml", &chains)?;
println!("Wrote: {}", routes); println!("Wrote: {}", routes);
Ok(()) Ok(())
} }
fn generate_route(route: &Route) -> String { fn generate_route(route: &Route) -> String {
match route.kind {
RouteKind::HTTP => generate_http_route(route),
RouteKind::TCP => generate_tcp_route(route),
}
}
fn generate_chains(routes: &[Route]) -> String {
let namespaces = routes
.iter()
.filter_map(|r| {
if !r.private {
return None;
}
Some(r.namespace.as_str())
})
.collect::<BTreeSet<_>>();
namespaces
.iter()
.enumerate()
.fold(String::new(), |mut acc, (i, n)| {
if i > 0 {
acc.push_str("\n---\n");
}
acc.push_str(&format!(
r#"apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: private-networks
namespace: {}
spec:
chain:
middlewares:
- name: private-networks
namespace: kube-system"#,
n
));
acc
})
}
fn generate_http_route(route: &Route) -> String {
let mut filters_section = String::new(); let mut filters_section = String::new();
if route.private { if route.private {
filters_section = format!( filters_section = format!(
r#"filters: r#"
filters:
- type: ExtensionRef - type: ExtensionRef
extensionRef: extensionRef:
group: traefik.io group: traefik.io
@@ -51,7 +108,7 @@ fn generate_route(route: &Route) -> String {
format!( format!(
r#"apiVersion: gateway.networking.k8s.io/v1 r#"apiVersion: gateway.networking.k8s.io/v1
kind: {} kind: HTTPRoute
metadata: metadata:
name: {} name: {}
namespace: {} namespace: {}
@@ -64,15 +121,49 @@ spec:
rules: rules:
- backendRefs: - backendRefs:
- name: {} - name: {}
port: {} port: {}{}"#,
{}"#,
route.name, route.name,
route.namespace, route.namespace,
route.hostname, route.hostname(),
route.service.clone().unwrap_or_else(|| route.name.clone()), route.service.as_ref().unwrap_or_else(|| &route.name),
route.port, route.port,
filters_section filters_section
) )
.trim_end() .trim_end()
.to_string() .to_string()
} }
fn generate_tcp_route(route: &Route) -> String {
let mut middlewares_section = String::new();
if route.private {
middlewares_section = format!(
r#"
middlewares:
- name: private-networks"#
);
}
format!(
r#"apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: {}
namespace: {}
spec:
entryPoints:
- {}
routes:
- match: HostSNI(`*`){}
services:
- name: {}
port: {}"#,
route.name,
route.namespace,
route.entrypoint,
middlewares_section,
route.service.as_ref().unwrap_or_else(|| &route.name),
route.port
)
.trim_end()
.to_string()
}