Compare commits

..
47 Commits
Author SHA1 Message Date
willemjan a01ec4b1e8 feat: add route to external firefly service 2026-11-15 14:33:33 +01:00
willemjan e1018464da feat: add route to external beszel service 2026-11-15 14:27:22 +01:00
willemjan 050d030cea feat: route to autotune service on proxmox 2026-11-15 14:06:39 +01:00
willemjan 02f43e8f54 fix(ddns): create records with proxied:false (DNS-only)
Cloudflare's edge 308-redirects git smart-http info/refs requests when the
record is proxied, breaking git/ArgoCD. DNS-only sends traffic straight to
the router -> node -> Traefik, which terminates TLS with a valid LE cert.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-14 22:44:47 +01:00
willemjan e48e67fe7c chore: repoURL gitea.zb-kw.duckdns.org -> gitea.labzand.com in all ArgoCD apps + root-app
Gitea is now served via Traefik on gitea.labzand.com (external-gitea);
the old duckdns host has no route once the router points at the node.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-14 22:33:00 +01:00
willemjan 530af4620c feat: add gitea route 2026-11-14 22:20:12 +01:00
willemjan 8b8784d0f2 feat: add homeassistant to treafik including dns record to reach from outside 2026-11-14 21:43:45 +01:00
willemjan 9bbdf7d5d6 fix: required tls 2026-11-14 21:34:01 +01:00
willemjan e8af901b7b feat: add omada external 2026-11-14 21:29:34 +01:00
willemjan a0be22c67c feat: add sonarqube route 2026-11-14 21:21:22 +01:00
willemjan f261cdde66 fix(external-proxmox): valid ingressroute with https backend + working endpoints
- ingress.yaml had invalid 'https:' rules key (dropped by apiserver -> OutOfSync);
  replaced by IngressRoute on websecure with scheme:https + ServersTransport
  (insecureSkipVerify) since Proxmox :8006 is https with self-signed cert
- EndpointSlice apply fails with kubectl 1.31 vs server 1.36 ('unknown field
  spec'), so keep v1 Endpoints (auto-mirrored to EndpointSlice); fixed spec
- shared namespace+middleware moved to services/external (app 'external')

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-14 21:08:52 +01:00
willemjan b9f5f5a2ae fix: address unqouted 2026-11-14 20:54:54 +01:00
willemjan f2ae7dde3e fix: add service to endpoint slice 2026-11-14 20:50:56 +01:00
willemjan e6b3ac4c4d fix: add endpoint slice 2026-11-14 20:45:56 +01:00
willemjan 69a3ed9ffe feat: moved the namespace and middleware to a spereate app and added proxmox stuff 2026-11-14 20:42:25 +01:00
willemjan 36ed5cf464 feat: traefik route to extenal adguard1 on proxmox lxc 2026-11-14 20:10:35 +01:00
willemjan ef58a7c0ca fix: drop lan-only ipAllowList (klipper-lb SNATs all LB traffic to 10.42.0.1, so Traefik never sees real client IPs)
Internal-only for argocd/auth/adguard.labzand.com is now enforced at DNS level:
cloudflare-ddns only publishes ns.labzand.com; internal names resolve solely via
AdGuard rewrites. Delete the *.labzand.com A record in Cloudflare once.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-14 17:35:35 +01:00
willemjan 43fdc020f4 feat: lan-only + force-ssl for argocd/authentik/adguard labzand.com ingresses
ipAllowList middleware (192.168.0.0/16) on the websecure routers blocks
external access; separate IngressRoute on web 301-redirects http->https.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-14 16:37:02 +01:00
willemjan e857cb989c feat: adguard admin console on adguard.labzand.com (internal-only, labzand wildcard cert)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-06 20:43:20 +01:00
willemjan 264815b2d2 feat: move argocd/authentik to *.labzand.com with Cloudflare DNS-01 wildcard certs
Internal-only hostnames (argocd.labzand.com, auth.labzand.com) via AdGuard
wildcard rewrite; certs still valid via DNS-01 so no browser warnings.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-06 20:37:30 +01:00
willemjan f585fa0aa9 fix: separate web-entrypoint IngressRoute for http->https redirect
A router with TLS enabled (from the Ingress tls section) never matches on
the plain web entrypoint, so entrypoints web,websecure on the Ingress itself
still 404'd on :80. Keep the Ingress on websecure only and add an
IngressRoute on web with the redirect-https middleware.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-06 20:20:23 +01:00
willemjan db1adfc229 feat: redirect http->https for ns.labzand.com (web entrypoint + redirect-https middleware)
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <[email protected]>
2026-11-06 20:10:38 +01:00
willemjan 789b9d0a79 fix: token 2026-11-04 21:01:54 +01:00
willemjan a9d8896081 fix: token 2026-11-04 20:59:29 +01:00
willemjan 7d13adf1a9 fix: location name 2026-11-04 20:46:12 +01:00
willemjan bf426524a5 feat: cloudflare cert issuer 2026-11-04 20:45:13 +01:00
willemjan ce4a4fdbd6 feat: rout to nightscout 2026-11-04 20:45:00 +01:00
willemjan a0a7d2d7ea feat: enabled proxy 2026-11-04 20:28:34 +01:00
willemjan 9df635c2ba fix: typo 2026-11-04 20:24:24 +01:00
willemjan 0fd234c88b fix: typo 2026-11-04 20:21:15 +01:00
willemjan d2804b0c51 removed kustomization 2026-11-04 20:15:17 +01:00
willemjan 202b5f0ee0 feat: cloudfare-ddns updater 2026-11-04 20:12:31 +01:00
willemjan 51823374ef fix missing http above paths 2026-11-04 16:12:37 +01:00
willemjan b81955b323 added secrets to authentik 2026-11-04 10:57:09 +01:00
willemjan e416add06d fix: typo and other suggestions 2026-10-31 16:18:46 +01:00
willemjan 8f8569cb82 feat: added authentik manifests 2026-10-31 16:08:23 +01:00
willemjan fada4e68b4 fix mounts 2026-10-16 17:17:14 +02:00
willemjan 9b1b4445ff Update services/adguard/app.yaml 2026-10-15 18:52:51 +00:00
willemjan 7ee1f967ae fix mounts adguard 2026-10-15 15:29:14 +02:00
willemjan a701b1e85b fix: volume mounts adguard 2026-10-15 15:16:24 +02:00
willemjan 8de01c331c fix namespace 2026-10-15 14:30:42 +02:00
willemjan 8a87d73efc add namespace to adguard destination 2026-10-15 09:47:40 +02:00
willemjan 07c5cb15ff removed namespace from application manifest 2026-10-15 09:32:46 +02:00
willemjan b5d859b6cb added adguard and agrocd self management 2026-10-15 09:23:32 +02:00
willemjan 221071a9fa added argocd install to git 2026-10-15 09:06:12 +02:00
willemjan 40e0cd05bb make ready for argo cd 2026-10-13 12:00:13 +02:00
willemjan 0b007c3d62 add webhook rbac 2026-10-13 08:52:23 +02:00
93 changed files with 3572 additions and 49 deletions
+2
View File
@@ -0,0 +1,2 @@
infrastructure/sealedsecret/sealed-secrets-key-BACKUP.yaml
pub-sealed-secrets.pem
+15
View File
@@ -0,0 +1,15 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: adguard
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/adguard
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated: {prune: true, selfHeal: true}
+16
View File
@@ -0,0 +1,16 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/argocd
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated: {prune: true, selfHeal: true}
+16
View File
@@ -0,0 +1,16 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: authentik
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/authentik
destination:
server: https://kubernetes.default.svc
namespace: authentik
syncPolicy:
automated: {prune: true, selfHeal: true}
+16
View File
@@ -0,0 +1,16 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cloudfare-ddns
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/cloudfare-ddns
destination:
server: https://kubernetes.default.svc
namespace: cloudfare-ddns
syncPolicy:
automated: {prune: true, selfHeal: true}
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-adguard
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-adguard
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-autotune
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external/external-autotune
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-beszel
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external/external-beszel
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-firefly
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external/external-firefly
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-gitea
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-gitea
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-homeassistant
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-homeassistant
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-nightscout
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-nightscout
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-omada
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-omada
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-proxmox
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-proxmox
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-sonarqube
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external-sonarqube
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=false
+18
View File
@@ -0,0 +1,18 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: services/external
destination:
server: https://kubernetes.default.svc
namespace: external
syncPolicy:
automated: {prune: true, selfHeal: true}
syncOptions:
- CreateNamespace=true
+21
View File
@@ -0,0 +1,21 @@
# bootstrap/root-app.yaml — de "app-of-apps"
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: default
source:
repoURL: https://gitea.labzand.com/willemjan/cluster.git
targetRevision: main
path: apps # elke yaml hierin = één Application
directory:
recurse: true
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true # wat uit Git verdwijnt, verdwijnt uit het cluster
selfHeal: true # handmatige kubectl-wijzigingen worden teruggedraaid
+6
View File
@@ -0,0 +1,6 @@
# infrastructure/argocd/values.yaml of patch
spec:
template:
spec:
nodeSelector:
node-type: 8100t
@@ -0,0 +1,27 @@
# ClusterIssuer voor Let's Encrypt via Cloudflare DNS-01 (voor labzand.com).
# Vereist een secret in namespace cert-manager (ClusterIssuer mag alleen
# secrets uit de cert-manager namespace lezen), bijv. via sealed-secrets:
#
# apiVersion: v1
# kind: Secret
# metadata:
# name: cloudflare-api-token
# namespace: cert-manager
# stringData:
# api-token: <CLOUDFLARE_API_TOKEN>
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cloudflare-dns
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: cloudflare-dns-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
@@ -0,0 +1,13 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: cloudflare-api-token
namespace: cert-manager
spec:
encryptedData:
api-token: AgBeOIVgJuFtfm4XMy9NqTCqaJ5Gmckk95koM0g6frGZA/FvU87hVGl9rwL/eZ9lSPAdEBTVXelo0pi5cJfFebhjoJfzse3TcfoJGPo5B41167pyrsVsfSailk3ZPwB6rPWoWMRGtct4SLIK4makh2xTE9ez7rZRxEXi8DGPWf7Bn7IIiV27R4MARR6SWeB9F4svvbJOgAfRjf+7pY95epHaFiNy2BUBkjrT6To98XlhsENNgLTTiS8kj6guT5BoPM+hKzQnHF+fCF6nucyddnnYvfxptjSNLEoDY2b2Iy2OUimb7arZXjmGBhAyH4ZQLsLQbF6OrixlR1Q60XsGcZkXebEC7nBDiaAvs7FFUeBO4gdvWIUeADqRkJLxETL3nodhFPjV6MaDJcH4ZGG4FOHA7ytWJ1n6+RJ0WaxC+JcYi5R6DvqNOFPJsvaKcr8aeG5YdrCLYeXzU95huNkX/AnDgAqcXVJAaWI+LN40B6Oq5z2lyz/oO1edOk170G52Qm7D+NwYq5/Dzm6oCuct2jieu/oI07784Ja1r9bMobIJr7cTBe2mHoEDvencFrc/vLViRXj/B6lfxQOcrkaKa+Bu4hOGlmBkLEqAlhyxVfSXk3EO6iqveahcdMmhngtrxmcjJW2WZQUNMBAGxo8kGGas8UFjzfdLD4zAqSl4Tmz6zcy3xO1z0FHfDRYplmAmFU+ji2C5qQVIczZOzAIuO6lN0JXUpo8Bk+jAZiM5Ic8RYQud30j+gQZMhFQfyRy99qdbOC6Ihr0=
template:
metadata:
name: cloudflare-api-token
namespace: cert-manager
@@ -1,24 +1,3 @@
# infrastructure/cert-manager/duckdns-issuer.yaml
# apiVersion: cert-manager.io/v1
# kind: ClusterIssuer
# metadata:
# name: duckdns
# spec:
# acme:
# server: https://acme-v02.api.letsencrypt.org/directory
# email: [email protected]
# privateKeySecretRef:
# name: duckdns-account-key # account-key wordt hierin opgeslagen
# solvers:
# - dns01:
# webhook:
# groupName: acme.duckdns.org # <-- dit is de juiste waarde
# solverName: duckdns
# config:
# apiTokenSecretRef: # <-- let op: niet secretName/subdomain!
# name: duckdns-token
# key: token
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
@@ -0,0 +1,25 @@
# infrastructure/cert-manager/webhook-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cert-manager-webhook-duckdns-token-reader
namespace: cert-manager
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["duckdns-token"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cert-manager-webhook-duckdns-token-reader
namespace: cert-manager
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cert-manager-webhook-duckdns-token-reader
subjects:
- kind: ServiceAccount
name: cert-manager-webhook-duckdns
namespace: cert-manager
@@ -1,25 +0,0 @@
# infrastructure/namespaces/00-namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
name: networking # adguard, traefik-config
---
apiVersion: v1
kind: Namespace
metadata:
name: auth
---
apiVersion: v1
kind: Namespace
metadata:
name: dashboards
---
apiVersion: v1
kind: Namespace
metadata:
name: ci
---
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
+416
View File
@@ -0,0 +1,416 @@
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations: {}
labels:
name: secrets-unsealer
name: secrets-unsealer
rules:
- apiGroups:
- bitnami.com
resources:
- sealedsecrets
verbs:
- get
- list
- watch
- apiGroups:
- bitnami.com
resources:
- sealedsecrets/status
verbs:
- update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
- create
- update
- delete
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
---
apiVersion: v1
kind: ServiceAccount
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
spec:
minReadySeconds: 30
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
name: sealed-secrets-controller
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
spec:
containers:
- args: []
command:
- controller
env: []
image: docker.io/bitnami/sealed-secrets-controller:0.41.2
imagePullPolicy: IfNotPresent
livenessProbe:
httpGet:
path: /healthz
port: http
name: sealed-secrets-controller
ports:
- containerPort: 8080
name: http
- containerPort: 8081
name: metrics
readinessProbe:
httpGet:
path: /healthz
port: http
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
stdin: false
tty: false
volumeMounts:
- mountPath: /tmp
name: tmp
imagePullSecrets: []
initContainers: []
securityContext:
fsGroup: 65534
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
serviceAccountName: sealed-secrets-controller
terminationGracePeriodSeconds: 30
volumes:
- emptyDir: {}
name: tmp
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-service-proxier
name: sealed-secrets-service-proxier
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sealed-secrets-service-proxier
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:authenticated
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: sealed-secrets-key-admin
subjects:
- kind: ServiceAccount
name: sealed-secrets-controller
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations: {}
labels:
name: sealed-secrets-key-admin
name: sealed-secrets-key-admin
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- list
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: sealedsecrets.bitnami.com
spec:
group: bitnami.com
names:
kind: SealedSecret
listKind: SealedSecretList
plural: sealedsecrets
singular: sealedsecret
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: |-
SealedSecret is the K8s representation of a "sealed Secret" - a
regular k8s Secret that has been sealed (encrypted) using the
controller's key.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: SealedSecretSpec is the specification of a SealedSecret.
properties:
data:
description: Data is deprecated and will be removed eventually. Use
per-value EncryptedData instead.
format: byte
type: string
encryptedData:
additionalProperties:
type: string
type: object
x-kubernetes-preserve-unknown-fields: true
template:
description: |-
Template defines the structure of the Secret that will be
created from this sealed secret.
properties:
data:
additionalProperties:
type: string
description: |-
Keys that should be templated using decrypted data.
Set a key with the same name as in `encryptedData`
to `null` to omit the unsealed data from the final secret.
nullable: true
type: object
immutable:
description: |-
Immutable, if set to true, ensures that data stored in the Secret cannot
be updated (only object metadata can be modified).
If not set to true, the field can be modified at any time.
Defaulted to nil.
type: boolean
metadata:
description: |-
Standard object's metadata.
More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
nullable: true
properties:
annotations:
additionalProperties:
type: string
type: object
finalizers:
items:
type: string
type: array
labels:
additionalProperties:
type: string
type: object
name:
type: string
namespace:
type: string
type: object
x-kubernetes-preserve-unknown-fields: true
type:
description: Used to facilitate programmatic handling of secret
data.
type: string
type: object
required:
- encryptedData
type: object
status:
description: SealedSecretStatus is the most recently observed status of
the SealedSecret.
properties:
conditions:
description: Represents the latest available observations of a sealed
secret's current state.
items:
description: SealedSecretCondition describes the state of a sealed
secret at a certain point.
properties:
lastTransitionTime:
description: Last time the condition transitioned from one status
to another.
format: date-time
type: string
lastUpdateTime:
description: The last time this condition was updated.
format: date-time
type: string
message:
description: A human readable message indicating details about
the transition.
type: string
reason:
description: The reason for the condition's last transition.
type: string
status:
description: |-
Status of the condition for a sealed secret.
Valid values for "Synced": "True", "False", or "Unknown".
type: string
type:
description: |-
Type of condition for a sealed secret.
Valid value: "Synced"
type: string
required:
- status
- type
type: object
type: array
observedGeneration:
description: ObservedGeneration reflects the generation most recently
observed by the sealed-secrets controller.
format: int64
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
namespace: kube-system
spec:
ports:
- port: 8080
targetPort: 8080
selector:
name: sealed-secrets-controller
type: ClusterIP
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations: {}
labels:
name: sealed-secrets-service-proxier
name: sealed-secrets-service-proxier
namespace: kube-system
rules:
- apiGroups:
- ""
resourceNames:
- sealed-secrets-controller
resources:
- services
verbs:
- get
- apiGroups:
- ""
resourceNames:
- 'http:sealed-secrets-controller:'
- http:sealed-secrets-controller:http
- sealed-secrets-controller
resources:
- services/proxy
verbs:
- create
- get
---
apiVersion: v1
kind: Service
metadata:
annotations: {}
labels:
name: sealed-secrets-controller-metrics
name: sealed-secrets-controller-metrics
namespace: kube-system
spec:
ports:
- port: 8081
targetPort: 8081
selector:
name: sealed-secrets-controller
type: ClusterIP
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations: {}
labels:
name: sealed-secrets-controller
name: sealed-secrets-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secrets-unsealer
subjects:
- kind: ServiceAccount
name: sealed-secrets-controller
namespace: kube-system
File diff suppressed because it is too large Load Diff
+102
View File
@@ -0,0 +1,102 @@
# services/<naam>/app.yaml
apiVersion: v1
kind: Namespace
metadata:
name: adguard
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: adguard-config
namespace: adguard
spec:
storageClassName: nfs-client # NFS vanaf Proxmox — Bijlage A
accessModes: [ReadWriteMany]
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: adguard
namespace: adguard
spec:
selector:
matchLabels: {app: adguard}
template:
metadata:
labels: {app: adguard}
spec:
containers:
- name: adguard
image: adguard/adguardhome:v0.107.79 # pin de tag! (Renovate houdt ze bij)
args: ["-c", "/opt/adguardhome/work/AdGuardHome.yaml", "-w", "/opt/adguardhome/work"]
ports:
- containerPort: 53 #dns
name: dns
protocol: UDP
- containerPort: 3000 #initial setup
name: http-initial
- containerPort: 80 #web gui
name: http
protocol: TCP
volumeMounts:
- name: adguard-config
mountPath: /opt/adguardhome/work
volumes:
- name: adguard-config
persistentVolumeClaim:
claimName: adguard-config
---
apiVersion: v1
kind: Service
metadata:
name: adguard-service
namespace: adguard
spec:
selector:
app: adguard
ports:
- protocol: TCP
port: 3000
targetPort: 3000
name: http-initial
- protocol: TCP
port: 80
targetPort: 80
name: http # intern op 80 zetten is handig
---
# toevoegen in service.yaml — DNS op de node zelf
apiVersion: v1
kind: Service
metadata:
name: adguard-dns
namespace: adguard
spec:
type: LoadBalancer # klipper bindt poort 53 op de node(s)
selector: {app: adguard}
ports:
- {name: dns-tcp, port: 53, targetPort: 53, protocol: TCP}
- {name: dns-udp, port: 53, targetPort: 53, protocol: UDP}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: adguard
namespace: adguard
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
rules:
- host: adguard.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: {name: adguard-service, port: {number: 80}}
tls:
- hosts: [adguard.labzand.com]
secretName: labzand-wildcard-tls
+14
View File
@@ -0,0 +1,14 @@
# services/<naam>/cert.yaml
# Wildcard-certificaat voor *.labzand.com via Cloudflare DNS-01.
# Vervangt de oude duckdns-cert (zb-kw-wildcard-tls).
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: labzand-wildcard
namespace: adguard
spec:
secretName: labzand-wildcard-tls
dnsNames: ["*.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,19 @@
# HTTP (entrypoint web) -> HTTPS redirect voor adguard.labzand.com.
# services: api@internal is een dummy - de redirect middleware stuurt
# nooit door naar een backend.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: adguard-redirect
namespace: adguard
spec:
entryPoints:
- web
routes:
- match: Host(`adguard.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+11
View File
@@ -0,0 +1,11 @@
# Middleware voor adguard.labzand.com: http (web) -> https redirect.
# (lan-only verwijderd: klipper-lb SNAT't al verkeer naar 10.42.0.1, zie argocd/middleware.yaml)
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: adguard
spec:
redirectScheme:
scheme: https
permanent: true
+13
View File
@@ -0,0 +1,13 @@
# Wildcard-certificaat voor *.labzand.com via Cloudflare DNS-01.
# DNS-01 werkt ook voor hostnames die niet publiek bereikbaar zijn.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: labzand-wildcard
namespace: argocd
spec:
secretName: labzand-wildcard-tls
dnsNames: ["*.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
+7 -3
View File
@@ -3,9 +3,13 @@ kind: Ingress
metadata:
name: argocd
namespace: argocd
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: argocd.zb-kw.duckdns.org
- host: argocd.labzand.com
http:
paths:
- path: /
@@ -16,5 +20,5 @@ spec:
port:
number: 80 # poort 80 van de svc = HTTP-backend
tls:
- hosts: [argocd.zb-kw.duckdns.org]
secretName: zb-kw-wildcard-tls
- hosts: [argocd.labzand.com]
secretName: labzand-wildcard-tls
@@ -0,0 +1,19 @@
# HTTP (entrypoint web) -> HTTPS redirect voor argocd.labzand.com.
# services: api@internal is een dummy - de redirect middleware stuurt
# nooit door naar een backend.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: argocd-redirect
namespace: argocd
spec:
entryPoints:
- web
routes:
- match: Host(`argocd.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+13
View File
@@ -0,0 +1,13 @@
# Middleware voor argocd.labzand.com: http (web) -> https redirect.
# (lan-only ipAllowList verwijderd: k3s klipper-lb SNAT't al LB-verkeer naar
# 10.42.0.1, waardoor Traefik nooit het echte client-IP ziet. Internal-only
# wordt afgedwongen via DNS: geen publiek A-record voor interne namen.)
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: argocd
spec:
redirectScheme:
scheme: https
permanent: true
+13
View File
@@ -0,0 +1,13 @@
# Wildcard-certificaat voor *.labzand.com via Cloudflare DNS-01.
# Vervangt de oude duckdns-cert (zb-kw-wildcard-tls).
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: labzand-wildcard
namespace: authentik
spec:
secretName: labzand-wildcard-tls
dnsNames: ["*.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
+125
View File
@@ -0,0 +1,125 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: authentik-media-pvc
namespace: authentik
spec:
storageClassName: nfs-client # NFS vanaf Proxmox — Bijlage A
accessModes: [ReadWriteMany]
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-server
namespace: authentik
spec:
replicas: 1
selector:
matchLabels: {app: authentik-server}
template:
metadata:
labels: {app: authentik-server}
spec:
initContainers:
- name: wait-for-db
image: busybox:latest # pin de tag! (Renovate houdt ze bij)
command:
- sh
- -c
- |
until nc -z authentik-postgresql 5432; do
echo "Waiting for PostgreSQL..."
sleep 2
done
echo "PostgreSQL is ready!"
containers:
- name: authentik
image: ghcr.io/goauthentik/server:2026.5.8
args: ["server"]
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_SECRET_KEY
- name: AUTHENTIK_POSTGRESQL__HOST
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__HOST
- name: AUTHENTIK_POSTGRESQL__PORT
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__PORT
- name: AUTHENTIK_POSTGRESQL__NAME
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__NAME
- name: AUTHENTIK_POSTGRESQL__USER
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__USER
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__PASSWORD
- name: AUTHENTIK_REDIS__HOST
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_REDIS__HOST
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
value: "false"
ports:
- containerPort: 9000
name: http
- containerPort: 9443
name: https
volumeMounts:
- name: media
mountPath: /media
livenessProbe:
httpGet:
path: /-/health/live/
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /-/health/ready/
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: media
persistentVolumeClaim:
claimName: authentik-media-pvc
---
apiVersion: v1
kind: Service
metadata:
name: authentik-server
namespace: authentik
spec:
selector: {app: authentik-server}
ports:
- port: 9000
targetPort: 9000
name: http
- port: 9443
targetPort: 9443
name: https
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: authentik
namespace: authentik
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: auth.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: authentik-server
port:
number: 9000
tls:
- hosts:
- auth.labzand.com
secretName: labzand-wildcard-tls
+37
View File
@@ -0,0 +1,37 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authentik-forwardauth
namespace: authentik
spec:
forwardAuth:
address: http://authentik-server.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: authentik-forwardauth-with-cookies
namespace: authentik
spec:
forwardAuth:
address: http://authentik-server.authentik.svc.cluster.local:9000/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
- X-authentik-name
- X-authentik-uid
- Set-Cookie
- Cookie
authRequestHeaders:
- X-Forwarded-Proto
- X-Forwarded-Host
- X-Forwarded-Uri
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: authentik
+65
View File
@@ -0,0 +1,65 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: authentik-postgres-pvc
namespace: authentik
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-postgresql
namespace: authentik
spec:
replicas: 1
selector:
matchLabels: {app: authentik-postgresql}
template:
metadata:
labels: {app: authentik-postgresql}
spec:
containers:
- name: postgresql
image: postgres:16-alpine
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: authentik-postgres-secret
key: POSTGRES_PASSWORD
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: authentik-postgres-secret
key: POSTGRES_USER
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: authentik-postgres-secret
key: POSTGRES_DB
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: authentik-postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: authentik-postgresql
namespace: authentik
spec:
selector:
app: authentik-postgresql
ports:
- port: 5432
+27
View File
@@ -0,0 +1,27 @@
# HTTP (entrypoint web) -> HTTPS redirect voor auth.labzand.com.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: authentik
spec:
redirectScheme:
scheme: https
permanent: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: auth-redirect
namespace: authentik
spec:
entryPoints:
- web
routes:
- match: Host(`auth.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+46
View File
@@ -0,0 +1,46 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-redis
namespace: authentik
spec:
replicas: 1
selector:
matchLabels: {app: authentik-redis}
template:
metadata:
labels: {app: authentik-redis}
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
name: redis
command:
- redis-server
- --save
- "60"
- "1"
- --logLevel
- warning
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
name: authentik-redis
namespace: authentik
spec:
selector: {app: authentik-redis}
ports:
- port: 6379
targetPort: 6379
name: redis
@@ -0,0 +1,21 @@
{
"kind": "SealedSecret",
"apiVersion": "bitnami.com/v1alpha1",
"metadata": {
"name": "authentik-postgres-secret",
"namespace": "authentik"
},
"spec": {
"template": {
"metadata": {
"name": "authentik-postgres-secret",
"namespace": "authentik"
}
},
"encryptedData": {
"POSTGRES_DB": "AgBlewltIxvwrrfMzpf1dlutVlStrPAlVmmTpRn7eBJ7CBfLKVBeiI1XoJXERFJl1PzOjO2by3J0/1CI3uAjcDq06CWyUHWNGdFsz/vkf570I3WFtSt5jeaNZtwgDdY6HRrvb23VAu3qqQtWuefPhIpUjqJAjNm+CktHAaF9ZS8VjvQ0zlFbeorZiIQf4W5rInfVKzEIrwrK1XMhXU7IM7ckWqZxI13KjGFqdlr2knTqbtNz8s6jID7V5sMk8v9eyerxv0bzTaiJdKi36SaH/SuDjETSqOEH6WEEjKgVvB5EBLFRBUwkV8EG8rVuNpGc3Fd9RhZX7ehVOGvmKFdvEmYrhNBV5JKUPj7beLdqP9wIKXvG1/R+Md5PhtWDbguXTVwHzTzFmMl2KfFjIR/jDhb64mUM6/SHphko+N48OQHPfyhTRZpDV8K3SfMo352atj+gLFEn42TWT+0D1rf84oGX0ztQltQMHv0xQuBRwKR6FJnYnt7ii8x3J+p8tQnd/JhvzaDt81f7GQv5avv/SFlvh/XxXGsZRz7695bzmOpxT1AEn9Zn/SCtIT0xX752trCcRaKV/9Jm2kOFWcCEm3ynQOlojLKugzoFiensfxKtywoTyESPSSIessSMX4hSezDGaprWjPG/ChK52IZz2LJHfdWyKgpYaQi/lPN6Jtq01wlKquhvs6UM6L8YIfL9NfsuWxRB7IsSZAI=",
"POSTGRES_PASSWORD": "AgCZncbrbavs+MADjKwVQ+Euk93PFHiyvMTdsDKFr6bRR0JkemG2vXwJURlUkkjExSpMvnHL6RhkXbtvhP53tFWnf8tqyk5Wg1ARrNg/H4LOobvLUwmKMEcpkqNj+w/585ijR6X0oFxxbh6dHyMjert4ASuMR8H2wlGChzE7GHp3PjZaPUlJAWk0iIBVQ3R9U9BavxbXa0zaoJQBEu6SZ8MNzyq14PPhLG2XD2k8qu4KyEIxAJhh+YO28GXVAQItSYui7T4S4UsRb3GH83rTBs97Wjn2PzGK0dl2D6Oc/Tol7UGbqTV00P5kVxc6mVOzw+aWVZekBw6KjrZ0sqWBQi/DtoFnECQkvERbEpQ5WaifsHjHnAf7o9DBjM2Lsn+UK5Ti1OMG5u3kcZAqo6opijjyGTxpyq51T2bZZpHbd2//k0aBax65fSo7YSqSEjICTu6LKGUprgCNfJC1Yi+GfXUVqDF6sA/gpcgkRp0zztaUoNXypzIfZQIEnTeCTncHDgwgTM6CM5l+ZUB4ApmbEzqP16SFojlGGVfUzeM8r2+KO/oUaFJI+QbVOU1gS5CQEEJ+ZdLsvYAaTPp/KkqEkSYQ2FkII8bGKBJjio8M8KTozSCDEiA7cBBnCqOI54F1vqrsCLcGFuwj910NoXUfyYzp5Sv4IiTVHWB7MboooLfAJ6X6Xzzjp6nthrmmPENXUitDo5eFOrQ/SrYIYjm7NRwAO7cCretlxqYFnSOL6ojLhz1Xb3aCltV7U0T3ZA==",
"POSTGRES_USER": "AgAMSJLtQvx4XygOyFrtJhfMwd6ylleyYtajd1TKQm47s5ZJVvQkBvVBN4LN29wvCbVC+Umu9EoXmP9ugIB96LTApPAQAt0q+IuLYrHwvOFRbrjubpqkDJh5GpSvogXLIQ4oTUO9C4n8++Lvun6695DYEck4ezcRp8tP2syXQc1UHHMQexfnhM9fIRuopvpWUc0Iqb73wh6MofbpCGR5sZHPQ35l65ZovmOv8gAKZbCcDT63fHN903kYbm9fqNSmyBLiBXn5qDKWz1LZrIPZ/BGgmNMfLvwKvmjSNLrP8iNgOzv2I1aeR7g+ss61T3GHHNno7IswAJWmUOTg1NCs4SEVOD4FthmiIlnAd8VkcuICiufduRMxQ1wTfIKCd/s4+u4YrNRmIxiFAG2G87GoFC6vO7QxbD7rLMxJsRrAf2x8RwaY3f1p/OWgyfqqdwkrLmp3VAVnibyaNdvkGfkj0y+kOQ3kZrS/zdgww6vrWo3Bziwj1ZwVtX7A32nw8LeeKlaQMbmjsazN8fH4sjWuXiy2wkn/XJsRHNo2za/45XlLbfH9JJZazDRDreAiwBD21t1ogsqAGPd/WlD5FzD4XU7qiLqHQZfxlcXOYmq2N19gmUo41bs4RBfwGpLzUz+3Qg3sqj79WvHgH4JWWi07MsmRy9q9/FSrYJRpNm8x3/s069eVL2XgWzCLlSZUx3D9DAyipoVdooGlE2k="
}
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"kind": "SealedSecret",
"apiVersion": "bitnami.com/v1alpha1",
"metadata": {
"name": "authentik-secret",
"namespace": "authentik"
},
"spec": {
"template": {
"metadata": {
"name": "authentik-secret",
"namespace": "authentik"
}
},
"encryptedData": {
"AUTHENTIK_POSTGRESQL__HOST": "AgCW4vQ2sjaRFWrzyMPRwXl5FdFY4gWrzqYrmxeO4RtylWuJ+USekNtPLbS8ibPN1w8s0Agwl4Jb5zJ1C/R2Y8C1vP4FugMSWf7F7Vm9DaP7weDrq7EC6pRPeK1b8Bqlkl1hPhzZloOX5LfCYY9laD5L3UGsXqC8Z2E06EduK1J8fnpY8OfawWppz79TkwNPGngGJk7to9aZI6SGVnnwZ4CdH3P3LTck0IHC3VJKYyxdK9XmQ4Msdd4lDRvA2rdDfg+2EvwRCeJ6m7qTtSL8Z1vCBMYEmyifb3UBake6nDj71OOacYWoIk4CUY39gS5MlQqqe+dftZmI4lvx8v5ViFYhaO1fI0EkQOTZwaLcvm5LZtvVwwsFWnBiI11sUl//gyDRz+5SVvlGhOA0rpPxZ2pqe0VKcFZicE7A+Nw3HYkbKFm3G9lfBG4YOIPnA4SX6Q857AJ2RRXk1Zu1jvzGVAm39iL5xNlHV1dbpsJpnGiTmnEL7UrQtcLy6mJyqJAuo/HhzKFsFpEllMFWoROoxK2zfr7hfZIA8j1kPoTh5Xs907MnJJWPQZX6hAII8MObwgaM2XOxkP1cBi5NIMQaPcrcER7uaMjzmYMNXQSaI7tFOExJquH64RStWts/LBH7xUuy5NSzs/1Lyn83V7+wQTMe+sP++x0b8ResIgq2LW8l+ZuEBBR4Q93MQTTJP/yZ0P5o1lFNqfMOhAKjCOr0HI4E45a85w==",
"AUTHENTIK_POSTGRESQL__NAME": "AgAlE5AU+tZ4+c7NIeBWsi8Q4mgaEO2znOFWp5NOUCBoM+KYG0aDWGmk7MBP2HtbdlJlkUs51IYWoaDb6tnV3AwQVB5WPaVI9dBVwiBdsBw0XVdwn08Hbp6mfiURqLNVAuqM44d1bep1ddiRi44+1Pa27bceO/ROVNyQEHjutPka6Tys7rXzEAIlCGeyI1ZcpsDGAFtgwQyBfQHNxKl8d0rgviLBXzTsuc4KJRN5Y6WV4GzgZa9iD3EacRcdp3hLSX8LqsswWpvASUMrmtymO9l1bI3lUh46QQuCAzWXKHiW7GkcZHSdPjvQsZQKYeO/LrkMknUuMiq60ETusyM23GqOz6hXLXmJhKtsc1x3AvzZDCxCW1SwcHKr9QuUH55gKgjm3ms4zrAaoECfcSH3ZzwnASIRgT+BiQRUfleyG4P5GgxMzN0Zxog6w90EIGhsjegIN6PZwO13DdcBBfCRoYhZOax2shW4hrvZMxYo+l/VNqxNopy0cx2befyoy+e2J+qEOExaWA5S8XQelQQCl8fSwSt+Wn5uzZleAK+0xzo5ZycYXPbnqLpPxVuYi5edomH0K4qMP5xnYEcN090PExgiHkxtzD1lv5QIVPY9NsYRtikdSf1NQ1NXuA6dgJBp80ZnTJpYKDr9v06yvJ+I2TVzHL87AVF+9RZEV3BIUIX6nBrcuBXHN4qeNhudio5VFp879/CwyAkPMN4=",
"AUTHENTIK_POSTGRESQL__PASSWORD": "AgCq22ubUvWQUrr7ztj2po2YMTFlKoWOy00QE7GNr6I6Fn8MkA61d//6HEhWpLJ9ETJogClDZEKLhNKrNtwiAXXw82gfUkzI51nJm+VrEfaZQsLQJT7WxDHLdbpRQjsgMqa+sZfjaCTdozzFdMbelYKl3/GBPrOPV521P404F3d9Cx4+NI+FpGGKaukTd1fLIlEiayELbiRnDjdXODXnNJ6NNpFRvfqi1wEVZ58HTn0tIVdJUrcBM+WY7up5MMwQJyK0dZ6NFpeUFQpd/W25lyuaPiSaYCLWLKSDMtKKmCfzk/J3VZ5s+yFffQa/sitnFtiwKweRH3xU5mJiYw4zZbiFg5Q3O25O++Oth1jbOMJvKJBz9soFcrC+GSxZMy2qxZHSXQdNLbeNlt5VWfdLcNLEgZKxoX3BzIk8h5I4B88rGX5c1/BhhkerrE+WMg79p5d8mnnA9TwX+3JFTkHteeEj7dcsutguLMd+9q84NXlF2sUFgN6+4Zo45FkmIkILd/ZasC+0W8NxJSxEg1BrZJh5ir2Xy3A1y0luzFy2f25U7OkwqC6bpg1pxTvMTWecjZ0krejp73yWDtJzw7xDawg3uwAWIe2PbQnnM5mMfB0zxHe9ialH236MAPmdhr0HKIG0Uh/9LCoLDAX/t+8MemPB8HODzMEQfEBpzEtALtk0958+HIwjbYu9ehE4zzexHncA6cjeU/LhtTjgnOek+4mPoYSxn/gR+D95nM69RkeHDsJ39cMo4ODgopbJUw==",
"AUTHENTIK_POSTGRESQL__PORT": "AgAFriuia/LohPqNw8jP1EI7NJBVyAuWxPvm7CN5YUjYjb/YRlsRO0yN/Op+lnw0KfOJQw3fr0rz8ccBl/TPS/6x5AFStqAYt6WSfMuyAjD16IfVS4tWH6ZBamtN35u8pBjk6h64p4l3Cyo65L5/Aou1zhWU8LZ27HrngVJJOM13k8JtuAwSB3pTf+aMaJem5Jc8tY4MAdEtpwiib+KtWEMDG6PAPJACtgC6h9mZbIf1DC9jLSZaGA4O7S1za4GZb7Cef4Z43xQmAI94RaYemU55VF+jJucMUwpG6SQSWSGUHOFS3rPHCUK0gK7nNomL/ryaaHi2SU52XL1oVRwbq2MzAb3M6PEMzfl6eK4fHYM5+GPGf8pnsfpQfs1XZxUYNqGYfIHTcYfBj4ffY6TqBq9iLEMtkmpM+6Wxtc7IfdEBCYHsphRmOOwB/RAVMtpBW2gmzpJwrRLQ6r4r7JjOr55A0I0TTGSm7ijKnGY+GuYJkeKfJ9vM5/wnbGGIh0hWMbnKpzlP8r7bbRp9qqC8GE6YdorzJEnBHKaP6r7NgRUt+cQI9kh/KLTOydrtHx+X15LajklBqwPnqAqNCx+jDe8e26BOS1N9qT0/lLyR1io7ZljMnxXAhu0cPF4uxJscHfaisVM6X925kWOAJqmcncngTN/7V/pK4pbGi8+VTV09vvcgDZHIEDPRexbFUl2dfBFsWWd7",
"AUTHENTIK_POSTGRESQL__USER": "AgBQsWvJfbOp72ChEu2bdraPpYyIB34u95hvLHe+mDaJjGanrC1ifl/2spJJDc1Mi2BGUPxIAJVt8Fz0POC/lHaq5+kX8SeF/BLcjTXZeaKGlx1y1UpGFwcNF1VIJni/hqqFTc2Q3cMwv4OxEJgCzI9PGvduA7jJjzQDz9c5M9AzIbzte3GZEYvOHWMfG5yvPY+Vo14+CVDoLc7o/HRJ4AB/zWcAFEmK2pwh+4ZDA2CONkk6KwKy0KSk+bQne/6tS2hXkyT9MTna3KTYvQp5w6b9sprFb5lIsk5QgAYeEMkFwsAaBWbWuLttq+QTFAIIFiWGAw94ON7nGE+iPjLz+PL2uQmEEusy9Qeu5sP61gCS+mEGTQEWi9M65sgm4bL0tqt39Ty9UzJY0WTmbaC5ioixsriovirBllTHmz6UonfEVwZhiNTxgQSfEuwfs4Pj+0LFKJ1UJBeUFZYKHW7Ixnm/tUinbVqFvXd98HgEjgi0xKgsUwPFkHqlhEgdu3QVhGkV9lNHwjNDBrk3SXaZ9DSsrcCxGMDxlc11CRPuifYCRmBSx6OXJDBLD2fmqWsq8LqgLSjPIdK5KyD6G2nfP6DePTh5yKrnb6gbb0aUkVDB9jlWXD5kBCAFct6ql/AbwuraZHvfExo2fX1rX4/7i87hNMVhSiXcOtB+VAmMAf7mbKRXKIaO16JR5JMH52IFfUYGRxGyDOnAyGM=",
"AUTHENTIK_REDIS__HOST": "AgAGtlVDGAAGLwmEUIUi4NvFUYZ08kq+Q4ZyP84eMqSYR3U0fAFQ1VhCUVhsrHVIIM/WeWwr/kqp4F8qYFUBGRcezRLKObogapWtRc1lkb5ayERXZUF1H+E8S4YtEsa+Y6InH6DHm0hTIY970k6a1HwV0Y8VuCawuXCF6Rc9VIq88GTpXT4PYCrsYwoGBH+xH/sz85OBlf9YYKZYX4s0i+Wm6DYSFcdoi75IEm3p3RNXpkwBwdZJNKJ9msUGlKBjRvHLoi+T1tCqxfvzh5q6bWEZR4JphNhl3C8psxsxxFoRIRTzh+FwJJov1ps4y+E/G56RBa5ClSYQsa498PdqENlD7vROILmD44FbhOcHmfp7vltI401aJydAMWAvG637Ugmvfrd2H/rBHHDWipBrCB7ih+n1tPmWeDC9Uo4nVLz+GVGfGMb4fI7CJaSk1WTRwCsuwjnxUnD7js0hW3IEUzcPceUXe0SV88R6pyDrXZpBW5fTbfyBcwG5VjoZtEgmDEci/mrP2T1hPZ2JVumxdbNvlIdby7e9qElrpB+5ny0kHev13GOBga0ZpUVrSmFnGMAil0NMjqLmUPWqE5Yp66TF64PGJqejlf6q7+Fvy0r9OpWG8eN9Q+59OEdhDadswEImOu62BmX+oHsLi6UHxPdfUZrRGIzoFYeBCo2TVRzqk6MbmaiVut2m0HPRER47ownM4VNyS32w+KLhUGECYZk=",
"AUTHENTIK_SECRET_KEY": "AgBBfeEC5uZ2cENxnvO0uXPrBBRGZcqsCNvWEGSYFuUbrK57V2UfL1bj4Ka6D0HT8mJ4wdu4+fyzgcK+p8K3WlcyVEtTro2JYOxRme8Mzp/rB3tTSgqMU0d/7fhMwU9XunlEsJ0ZLSz4Kxc2SCzGf0jbfxEDKh+ThhqjITyCkkuBynIhnHqRWwi1bJByKKwEH9FtKyLLFiuJVIQLsbXirkW3mOnyEN632Jn0qbKDgqmO+fupRaa6bFSU6AzJYpzxiEnNjOlqKXO0kdBrrFhfHnoaLQ0daPLPIAWMfGhTAPXpukOgQHUgO2quRlM5+7Ipy+gMnGPu3mUJ0QXw9v/7b/EkIClYpLdxVcxovg1BFpcrsgJqiXWrA4M9VYBvuCkYx1LtIIKf8wGQFMIAk6jT+cAFHQgvl2zecd9vnaTcA1oPFvOHUOgc9nY611o+/HfJ7W6VswqCr+qhtp8boYCi/H8c4nFM+BcQ4luK3Asjv9Hd9lH5NrB59DBHQxP3mKy+H3fuIgMk8Bp1sPufQFjtspudj9G5VZKp3QhKNw707z/+9vulk8nvLa3gF3Iy0wGkDlpy71PW5LJBrsPNMXz4FLQLYHts2SsE21jSKr1XxEkzjvR/xbQchUbKpmdoBnqjv7RyDIezb8lzh1cAen/Icxwlz3K3IBbPbqlDmbcS/pnwosdXE/iQ3r9g0cxhDNjLvsL9uiMlVPFVl4RbrNRnkjJu+/vwlyQnBd+FKVQv32PzSeYg2WmMAjoR9RX0QQxoPFArSylNnKjEHhsxQ8onc9HojktXgXOH108oF4xB+bt3jFo="
}
}
}
+69
View File
@@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: authentik-worker
namespace: authentik
spec:
replicas: 1
selector:
matchLabels: {app: authentik-worker}
template:
metadata:
labels: {app: authentik-worker}
spec:
containers:
- name: authentik
image: ghcr.io/goauthentik/server:2026.5.8
args: ["worker"]
env:
- name: AUTHENTIK_SECRET_KEY
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_SECRET_KEY
- name: AUTHENTIK_POSTGRESQL__HOST
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__HOST
- name: AUTHENTIK_POSTGRESQL__PORT
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__PORT
- name: AUTHENTIK_POSTGRESQL__NAME
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__NAME
- name: AUTHENTIK_POSTGRESQL__USER
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__USER
- name: AUTHENTIK_POSTGRESQL__PASSWORD
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_POSTGRESQL__PASSWORD
- name: AUTHENTIK_REDIS__HOST
valueFrom:
secretKeyRef:
name: authentik-secret
key: AUTHENTIK_REDIS__HOST
- name: AUTHENTIK_ERROR_REPORTING__ENABLED
value: "false"
volumeMounts:
- name: media
mountPath: /media
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: media
persistentVolumeClaim:
claimName: authentik-media-pvc
+13
View File
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudflare-ddns-config
namespace: cloudflare-ddns
data:
# Comma-separated list of domains to update
# Format: subdomain.domain.tld,subdomain2.domain.tld
# Alleen namen die écht publiek bereikbaar moeten zijn (via router -> cluster).
# Interne-only namen (argocd/auth/adguard.labzand.com) bewust NIET hier:
# die krijgen geen publiek A-record en resolven alleen intern via AdGuard.
DOMAINS: "ns.labzand.com, ha.labzand.com, gitea.labzand.com"
ZONE_ID: "6093605ad5a8db13e9c9d9faad240ca6"
+50
View File
@@ -0,0 +1,50 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: cloudflare-ddns
namespace: cloudflare-ddns
spec:
# Run every hour at the top of the hour
schedule: "0 * * * *"
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
metadata:
labels:
app: cloudflare-ddns
spec:
restartPolicy: OnFailure
containers:
- name: cloudflare-ddns
image: curlimages/curl:latest
command:
- /bin/sh
- /scripts/update-dns.sh
env:
- name: DOMAINS
valueFrom:
configMapKeyRef:
name: cloudflare-ddns-config
key: DOMAINS
- name: ZONE_ID
valueFrom:
configMapKeyRef:
name: cloudflare-ddns-config
key: ZONE_ID
- name: API_TOKEN
valueFrom:
secretKeyRef:
name: cloudflare-ddns-secret
key: API_TOKEN
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
volumes:
- name: scripts
configMap:
name: cloudflare-ddns-script
defaultMode: 0755
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: cloudflare-ddns
labels:
name: cloudflare-ddns
@@ -0,0 +1,127 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudflare-ddns-script
namespace: cloudflare-ddns
data:
update-dns.sh: |
#!/bin/sh
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "Starting Cloudflare DDNS update..."
echo "Timestamp: $(date)"
# Check required environment variables
if [ -z "$API_TOKEN" ]; then
echo "${RED}ERROR: API_TOKEN is not set${NC}"
exit 1
fi
if [ -z "$ZONE_ID" ]; then
echo "${RED}ERROR: ZONE_ID is not set${NC}"
exit 1
fi
if [ -z "$DOMAINS" ]; then
echo "${RED}ERROR: DOMAINS is not set${NC}"
exit 1
fi
# Get current public IP address
echo "Fetching current public IP address..."
PUBLIC_IP=$(curl -s https://api.ipify.org)
if [ -z "$PUBLIC_IP" ]; then
echo "First attempt failed, trying alternative service..."
PUBLIC_IP=$(curl -s https://ifconfig.me)
fi
if [ -z "$PUBLIC_IP" ]; then
echo "${RED}ERROR: Could not determine public IP address${NC}"
exit 1
fi
echo "${GREEN}Current public IP: $PUBLIC_IP${NC}"
# Process each domain
echo "$DOMAINS" | tr ',' '\n' | while IFS= read -r DOMAIN; do
# Trim whitespace
DOMAIN=$(echo "$DOMAIN" | xargs)
echo ""
echo "Processing domain: $DOMAIN"
# Get DNS record ID for the domain
echo "Fetching DNS record for $DOMAIN..."
RECORD_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$DOMAIN&type=A" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json")
# Check if the request was successful
SUCCESS=$(echo "$RECORD_RESPONSE" | grep -o '"success":[^,]*' | cut -d':' -f2)
if [ "$SUCCESS" != "true" ]; then
echo "${RED}ERROR: Failed to fetch DNS record for $DOMAIN${NC}"
echo "$RECORD_RESPONSE"
continue
fi
# Extract record ID and current IP
RECORD_ID=$(echo "$RECORD_RESPONSE" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
CURRENT_IP=$(echo "$RECORD_RESPONSE" | grep -o '"content":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$RECORD_ID" ]; then
echo "${YELLOW}WARNING: DNS record not found for $DOMAIN. Creating new record...${NC}"
# Create new DNS record
CREATE_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$DOMAIN\",\"content\":\"$PUBLIC_IP\",\"ttl\":120,\"proxied\":false}")
CREATE_SUCCESS=$(echo "$CREATE_RESPONSE" | grep -o '"success":[^,]*' | cut -d':' -f2)
if [ "$CREATE_SUCCESS" = "true" ]; then
echo "${GREEN}Successfully created DNS record for $DOMAIN with IP $PUBLIC_IP${NC}"
else
echo "${RED}ERROR: Failed to create DNS record for $DOMAIN${NC}"
echo "$CREATE_RESPONSE"
fi
continue
fi
echo "Current DNS IP: $CURRENT_IP"
# Check if IP needs to be updated
if [ "$CURRENT_IP" = "$PUBLIC_IP" ]; then
echo "${GREEN}IP address is already up to date for $DOMAIN${NC}"
continue
fi
echo "${YELLOW}Updating DNS record for $DOMAIN from $CURRENT_IP to $PUBLIC_IP${NC}"
# Update DNS record
UPDATE_RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$DOMAIN\",\"content\":\"$PUBLIC_IP\",\"ttl\":120,\"proxied\":false}")
UPDATE_SUCCESS=$(echo "$UPDATE_RESPONSE" | grep -o '"success":[^,]*' | cut -d':' -f2)
if [ "$UPDATE_SUCCESS" = "true" ]; then
echo "${GREEN}Successfully updated DNS record for $DOMAIN to $PUBLIC_IP${NC}"
else
echo "${RED}ERROR: Failed to update DNS record for $DOMAIN${NC}"
echo "$UPDATE_RESPONSE"
fi
done
echo ""
echo "Cloudflare DDNS update completed at $(date)"
@@ -0,0 +1,19 @@
{
"kind": "SealedSecret",
"apiVersion": "bitnami.com/v1alpha1",
"metadata": {
"name": "cloudflare-ddns-secret",
"namespace": "cloudflare-ddns"
},
"spec": {
"template": {
"metadata": {
"name": "cloudflare-ddns-secret",
"namespace": "cloudflare-ddns"
}
},
"encryptedData": {
"API_TOKEN": "AgASyHk4o7tXdEY9QRROjxx51GDpzYQ13mj3hHYHNYkmQ5r3DH1j1sDZKv4Psf10krEhLdNzHPqbmn9IQYP6VyNIRG1E2YmTNBjx5ePp4TPSu0Ya/h6BGnCaMW8rcLSzy+dhsuPJby/PL7trQRvNkhCIY0YUZV2jmIHaWU8TQrBTHF6yxDFL98IePYM5VFLgajEz+kuVROVhxMXCW9jfKTzeWoI0edXCDx8qvFiMC9c8nBoLbbcLkXPxP0803S3kE8qX1BnCJN9gZSDbOPHkB7f9b/6JztjBK2cWhA8bjhHX6geC54PZJ9HRYoM9cghSg4ug3G02d0uQ186GSIELTYSLtHkOlYjuTcvs8PO5cwRR3sk2Dm06NyH2fAsWEfbE3OsOqyOgvEnOUt65li4MEbeBsjyrp2AY3xypOffbTgWKAnyTlaRMtDlUN4VERzc046lZAPEsPEadIcv2BarM3LllFPD8svXxNhieY+lihD5LAD7xqZkCxvPB3cjFwvFdU9ZSpC9J9108P5XKfvSsUa6O2cBC42VEd1BnsyWt0jsRhJrRHPpmtjrCP9kyzbByIp+tlML2LwXm42+9smr+cPesJuawk2texEmXERu3R9U7SidKizXOjrUl32oNgYFPPb18oFCluvyyry8g/MUs+gf4mug56lMU6uuHLqD+i74WL2jhyQe0lJeVkm9IVDykS5sJnoP02Sgb9b74lr9ctPK8CUVupamAV14OAYqi/9IjcGcZpgWw677cH+Kt8rbLlUrX8sFyVA=="
}
}
}
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-adguard-labzand
namespace: external
spec:
secretName: external-adguard-labzand-tls
dnsNames: ["adguard1.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: external-adguard
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: adguard1.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: adguard-external
port:
number: 80
tls:
- hosts:
- adguard1.labzand.com
secretName: external-adguard-labzand-tls
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-adguard-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`adguard1.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: adguard-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: adguard-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.53
ports:
- name: http
port: 80
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gitea-labzand
namespace: external
spec:
secretName: gitea-labzand-tls
dnsNames: ["gitea.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gitea
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: gitea.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gitea-external
port:
number: 3000
tls:
- hosts:
- gitea.labzand.com
secretName: gitea-labzand-tls
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: gitea-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`gitea.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+25
View File
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Service
metadata:
name: gitea-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 3000
targetPort: 3000
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: gitea-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.52
ports:
- name: http
port: 3000
protocol: TCP
+14
View File
@@ -0,0 +1,14 @@
# Certificaat voor ns.labzand.com via Cloudflare DNS-01.
# Vereist: ClusterIssuer 'cloudflare-dns' (infrastructure/cert-manager/cloudflare-issuer.yaml)
# en een secret 'cloudflare-api-token' in namespace cert-manager.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ha-labzand
namespace: external
spec:
secretName: ha-labzand-tls
dnsNames: ["ha.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: homeassistant
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: ha.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ha-external
port:
number: 8123
tls:
- hosts:
- ha.labzand.com
secretName: ha-labzand-tls
@@ -0,0 +1,21 @@
# HTTP (entrypoint web) -> HTTPS redirect voor ns.labzand.com.
# De Ingress zelf heeft een tls-sectie, waardoor Traefik die router alleen
# op websecure matcht; daarom hier een aparte router op web.
# services: api@internal is een dummy - de redirect middleware stuurt
# nooit door naar een backend.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: ha-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`ha.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: ha-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 8123
targetPort: 8123
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: ha-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.146
ports:
- name: http
port: 8123
protocol: TCP
+14
View File
@@ -0,0 +1,14 @@
# Certificaat voor ns.labzand.com via Cloudflare DNS-01.
# Vereist: ClusterIssuer 'cloudflare-dns' (infrastructure/cert-manager/cloudflare-issuer.yaml)
# en een secret 'cloudflare-api-token' in namespace cert-manager.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ns-labzand
namespace: external
spec:
secretName: ns-labzand-tls
dnsNames: ["ns.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ns
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: ns.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ns-external
port:
number: 1337
tls:
- hosts:
- ns.labzand.com
secretName: ns-labzand-tls
@@ -0,0 +1,21 @@
# HTTP (entrypoint web) -> HTTPS redirect voor ns.labzand.com.
# De Ingress zelf heeft een tls-sectie, waardoor Traefik die router alleen
# op websecure matcht; daarom hier een aparte router op web.
# services: api@internal is een dummy - de redirect middleware stuurt
# nooit door naar een backend.
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: ns-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`ns.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: ns-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 1337
targetPort: 1337
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: ns-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.12
ports:
- name: http
port: 1337
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-omada-labzand
namespace: external
spec:
secretName: external-omada-labzand-tls
dnsNames: ["omada.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-omada-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`omada.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+29
View File
@@ -0,0 +1,29 @@
# omada (:8043) spricht HTTPS met een self-signed cert — daarom een
# IngressRoute (i.p.v. een plain Ingress, die altijd HTTP naar de backend praat)
# met scheme: https en een ServersTransport die het cert niet checkt.
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
name: omada-insecure
namespace: external
spec:
insecureSkipVerify: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-omada
namespace: external
spec:
entryPoints:
- websecure
tls:
secretName: external-omada-labzand-tls
routes:
- match: Host(`omada.labzand.com`)
kind: Rule
services:
- name: omada-external
port: 8043
scheme: https
serversTransport: omada-insecure
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: omada-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 8043
targetPort: 8043
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: omada-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.62
ports:
- name: http
port: 8043
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-proxmox-labzand
namespace: external
spec:
secretName: external-proxmox-labzand-tls
dnsNames: ["proxmox.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-proxmox-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`proxmox.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
@@ -0,0 +1,29 @@
# Proxmox (:8006) spricht HTTPS met een self-signed cert — daarom een
# IngressRoute (i.p.v. een plain Ingress, die altijd HTTP naar de backend praat)
# met scheme: https en een ServersTransport die het cert niet checkt.
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
name: proxmox-insecure
namespace: external
spec:
insecureSkipVerify: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-proxmox
namespace: external
spec:
entryPoints:
- websecure
tls:
secretName: external-proxmox-labzand-tls
routes:
- match: Host(`proxmox.labzand.com`)
kind: Rule
services:
- name: proxmox-external
port: 8006
scheme: https
serversTransport: proxmox-insecure
+33
View File
@@ -0,0 +1,33 @@
# Service zonder selector: Kubernetes maakt zelf geen endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (Proxmox, buiten het cluster).
# De endpoints-controller mirrored dit automatisch naar een EndpointSlice.
# (Een handmatige discovery.k8s.io/v1 EndpointSlice faalt op deze cluster met
# kubectl 1.31 tegen server 1.36: "unknown field spec" — daher v1 Endpoints.)
# Let op: ArgoCD trackt Endpoints/EndpointSlice niet — apply deze file handmatig
# als het endpoint ontbreekt:
# kubectl apply -f services/external-proxmox/service.yaml
apiVersion: v1
kind: Service
metadata:
name: proxmox-external
namespace: external
spec:
type: ClusterIP
ports:
- name: https
port: 8006
targetPort: 8006
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: proxmox-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.64
ports:
- name: https
port: 8006
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-sonarqube-labzand
namespace: external
spec:
secretName: external-sonarqube-labzand-tls
dnsNames: ["sonarqube.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: external-sonarqube
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: sonarqube.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sonarqube-external
port:
number: 9000
tls:
- hosts:
- sonarqube.labzand.com
secretName: external-sonarqube-labzand-tls
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-sonarqube-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`sonarqube.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: sonarqube-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 9000
targetPort: 9000
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: sonarqube-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.49
ports:
- name: http
port: 9000
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-autotune-labzand
namespace: external
spec:
secretName: external-autotune-labzand-tls
dnsNames: ["autotune.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-autotune-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`autotune.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: external-autotune
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: autotune.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: autotune-external
port:
number: 8502
tls:
- hosts:
- autotune.labzand.com
secretName: external-autotune-labzand-tls
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: autotune-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 8502
targetPort: 8502
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: autotune-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.51
ports:
- name: http
port: 8502
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-beszel-labzand
namespace: external
spec:
secretName: external-beszel-labzand-tls
dnsNames: ["beszel.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-beszel-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`beszel.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: external-beszel
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: beszel.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: beszel-external
port:
number: 8090
tls:
- hosts:
- beszel.labzand.com
secretName: external-beszel-labzand-tls
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: beszel-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 8090
targetPort: 8090
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: beszel-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.61
ports:
- name: http
port: 8090
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: external-firefly-labzand
namespace: external
spec:
secretName: external-firefly-labzand-tls
dnsNames: ["firefly.labzand.com"]
issuerRef:
name: cloudflare-dns
kind: ClusterIssuer
@@ -0,0 +1,16 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: external-firefly-redirect
namespace: external
spec:
entryPoints:
- web
routes:
- match: Host(`firefly.labzand.com`)
kind: Rule
middlewares:
- name: redirect-https
services:
- name: api@internal
kind: TraefikService
+25
View File
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: external-firefly
namespace: external
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: firefly.labzand.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: firefly-external
port:
number: 80
tls:
- hosts:
- firefly.labzand.com
secretName: external-firefly-labzand-tls
+27
View File
@@ -0,0 +1,27 @@
# Service zonder selector: Kubernetes maakt zelf geen Endpoints aan.
# Het Endpoints-object hieronder levert het externe adres (buiten het cluster).
apiVersion: v1
kind: Service
metadata:
name: firefly-external
namespace: external
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
---
apiVersion: v1
kind: Endpoints
metadata:
name: firefly-external
namespace: external
subsets:
- addresses:
- ip: 192.168.2.59
ports:
- name: http
port: 80
protocol: TCP
+11
View File
@@ -0,0 +1,11 @@
# Redirect plain HTTP (entrypoint web) naar HTTPS.
# Op websecure is het scheme al https, dus daar doet deze middleware niets.
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: redirect-https
namespace: external
spec:
redirectScheme:
scheme: https
permanent: true
+6
View File
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: external
labels:
name: external