From bbba564dad3ab6648be5898c127e2cfc7f444b3a Mon Sep 17 00:00:00 2001 From: Cluster Backup Engine Date: Sun, 18 Oct 2026 13:47:06 +0000 Subject: [PATCH] Fresh cluster restore --- README.md | 2 + blocky/values.yaml | 88 ++++++ bookstack-db/resources.yaml | 313 ++++++++++++++++++++ bookstack/ingress.yaml | 29 ++ bookstack/resources.yaml | 40 +++ cloudflared/cloudflared-backup.yaml | 77 +++++ cobalt/resources.yaml | 150 ++++++++++ convertx/values.yaml | 82 ++++++ gitea/gitea-stack.yaml | 188 ++++++++++++ gitea/ingress.yaml | 29 ++ gitea/resources.yaml | 397 +++++++++++++++++++++++++ homepage/homepage.yaml | 99 +++++++ homepage/ingress.yaml | 29 ++ homepage/resources.yaml | 430 +++++++++++++++++++++++++++ immich/ingress.yaml | 33 +++ immich/values.yaml | 36 +++ kube-prometheus-stack/values.yaml | 42 +++ mumble/resources.yaml | 432 ++++++++++++++++++++++++++++ open-webui/ingress.yaml | 34 +++ open-webui/ollama-fallback.yaml | 68 +++++ open-webui/open-webui-stack.yaml | 116 ++++++++ open-webui/resources.yaml | 430 +++++++++++++++++++++++++++ portainer/values.yaml | 24 ++ qr-barcode-generator/resources.yaml | 74 +++++ searxng/searxng.yaml | 84 ++++++ 25 files changed, 3326 insertions(+) create mode 100644 README.md create mode 100644 blocky/values.yaml create mode 100644 bookstack-db/resources.yaml create mode 100644 bookstack/ingress.yaml create mode 100644 bookstack/resources.yaml create mode 100644 cloudflared/cloudflared-backup.yaml create mode 100644 cobalt/resources.yaml create mode 100644 convertx/values.yaml create mode 100644 gitea/gitea-stack.yaml create mode 100644 gitea/ingress.yaml create mode 100644 gitea/resources.yaml create mode 100644 homepage/homepage.yaml create mode 100644 homepage/ingress.yaml create mode 100644 homepage/resources.yaml create mode 100644 immich/ingress.yaml create mode 100644 immich/values.yaml create mode 100644 kube-prometheus-stack/values.yaml create mode 100644 mumble/resources.yaml create mode 100644 open-webui/ingress.yaml create mode 100644 open-webui/ollama-fallback.yaml create mode 100644 open-webui/open-webui-stack.yaml create mode 100644 open-webui/resources.yaml create mode 100644 portainer/values.yaml create mode 100644 qr-barcode-generator/resources.yaml create mode 100644 searxng/searxng.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc87041 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# cluster-manifests + diff --git a/blocky/values.yaml b/blocky/values.yaml new file mode 100644 index 0000000..1424439 --- /dev/null +++ b/blocky/values.yaml @@ -0,0 +1,88 @@ +controller: + replicas: 2 + strategy: RollingUpdate + +affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - blocky + topologyKey: "kubernetes.io/hostname" + +config: | + upstream: + default: + - https://1.1.1.1/dns-query + - https://8.8.8.8/dns-query + + customDNS: + customTTL: 1h + mapping: + needlestack.boo: 192.168.1.201 + "*.needlestack.boo": 192.168.1.201 + + blocking: + denylists: + ads: + - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts + clientGroupsBlock: + default: + - ads + blockType: NXDOMAIN + refreshPeriod: 4h + + caching: + minTime: 5m + maxTime: 30m + prefetching: true + + ports: + dns: 53 + http: 4000 + prometheus: + enable: true + path: /metrics + +service: + # The main service block MUST remain enabled on port 4000 for health checks and metrics + main: + enabled: true + ports: + http: + port: 4000 + dns: + enabled: true + type: LoadBalancer + # THE FIX: Allows Node 1 to proxy queries to your replicas on Node 2 & 3 + externalTrafficPolicy: Cluster + annotations: {} + ports: + dns-udp: + enabled: true + port: 53 + protocol: UDP + targetPort: 53 + dns-tcp: + enabled: true + port: 53 + protocol: TCP + targetPort: 53 + +metrics: + enabled: true + serviceMonitor: + interval: 30s + scrapeTimeout: 10s + labels: + release: kube-prometheus-stack + jobLabel: "" + targetLabels: [] + podTargetLabels: [] + prometheusRule: + enabled: false + labels: {} + rules: [] diff --git a/bookstack-db/resources.yaml b/bookstack-db/resources.yaml new file mode 100644 index 0000000..0405264 --- /dev/null +++ b/bookstack-db/resources.yaml @@ -0,0 +1,313 @@ +apiVersion: v1 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "3" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"bookstack-web","namespace":"bookstack-db"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"bookstack-web"}},"template":{"metadata":{"labels":{"app":"bookstack-web"}},"spec":{"containers":[{"env":[{"name":"PUID","value":"1000"},{"name":"PGID","value":"1000"},{"name":"TZ","value":"Europe/London"},{"name":"APP_URL","value":"https://wiki.needlestack.boo"},{"name":"APP_KEY","value":"base64:Jng4M2tkbXNleXBoYWRmZ2g0NTZqYm5tY3YxMjM0NTY="},{"name":"DB_HOST","value":"10.43.161.65"},{"name":"DB_PORT","value":"3306"},{"name":"DB_USER","value":"bookstack"},{"name":"DB_PASS","value":"Bar07ney!"},{"name":"DB_DATABASE","value":"bookstack"}],"image":"docker.io/linuxserver/bookstack:latest","name":"bookstack","ports":[{"containerPort":80,"name":"http"}],"volumeMounts":[{"mountPath":"/config","name":"bookstack-config"}]}],"dnsConfig":{"nameservers":["1.1.1.1","8.8.8.8"]},"dnsPolicy":"None","volumes":[{"name":"bookstack-config","persistentVolumeClaim":{"claimName":"bookstack-config-pvc"}}]}}}} + creationTimestamp: "2026-09-12T20:44:04Z" + generation: 5 + name: bookstack-web + namespace: bookstack-db + resourceVersion: "851484" + uid: 714317b1-19be-4e10-ba0d-7fbf23bae864 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: bookstack-web + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2026-09-12T21:36:57Z" + creationTimestamp: null + labels: + app: bookstack-web + spec: + containers: + - env: + - name: PUID + value: "1000" + - name: PGID + value: "1000" + - name: TZ + value: Europe/London + - name: APP_URL + value: https://wiki.needlestack.boo + - name: APP_KEY + value: base64:Jng4M2tkbXNleXBoYWRmZ2g0NTZqYm5tY3YxMjM0NTY= + - name: DB_HOST + value: 10.43.161.65 + - name: DB_PORT + value: "3306" + - name: DB_USER + value: bookstack + - name: DB_PASS + value: Bar07ney! + - name: DB_DATABASE + value: bookstack + image: docker.io/linuxserver/bookstack:latest + imagePullPolicy: Always + name: bookstack + ports: + - containerPort: 80 + name: http + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /config + name: bookstack-config + dnsConfig: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsPolicy: None + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: bookstack-config + persistentVolumeClaim: + claimName: bookstack-config-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-12T20:44:04Z" + lastUpdateTime: "2026-09-12T21:39:25Z" + message: ReplicaSet "bookstack-web-74b9c9c799" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + - lastTransitionTime: "2026-09-12T21:40:00Z" + lastUpdateTime: "2026-09-12T21:40:00Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + observedGeneration: 5 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "1" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"mariadb","namespace":"bookstack-db"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"mariadb"}},"template":{"metadata":{"labels":{"app":"mariadb"}},"spec":{"containers":[{"env":[{"name":"MARIADB_ROOT_PASSWORD","value":"nstBar07ney!"},{"name":"MARIADB_DATABASE","value":"bookstack"},{"name":"MARIADB_USER","value":"bookstack"},{"name":"MARIADB_PASSWORD","value":"Bar07ney!"}],"image":"docker.io/library/mariadb:10.11","name":"mariadb","ports":[{"containerPort":3306,"name":"mysql"}],"volumeMounts":[{"mountPath":"/var/lib/mysql","name":"mariadb-storage"}]}],"dnsConfig":{"nameservers":["1.1.1.1","8.8.8.8"]},"dnsPolicy":"None","volumes":[{"name":"mariadb-storage","persistentVolumeClaim":{"claimName":"mariadb-data-pvc"}}]}}}} + creationTimestamp: "2026-09-12T20:39:27Z" + generation: 1 + name: mariadb + namespace: bookstack-db + resourceVersion: "834544" + uid: 9a81a32b-2079-43ee-8394-643027df3986 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mariadb + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: mariadb + spec: + containers: + - env: + - name: MARIADB_ROOT_PASSWORD + value: nstBar07ney! + - name: MARIADB_DATABASE + value: bookstack + - name: MARIADB_USER + value: bookstack + - name: MARIADB_PASSWORD + value: Bar07ney! + image: docker.io/library/mariadb:10.11 + imagePullPolicy: IfNotPresent + name: mariadb + ports: + - containerPort: 3306 + name: mysql + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /var/lib/mysql + name: mariadb-storage + dnsConfig: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsPolicy: None + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: mariadb-storage + persistentVolumeClaim: + claimName: mariadb-data-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-12T20:40:01Z" + lastUpdateTime: "2026-09-12T20:40:01Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: "2026-09-12T20:39:27Z" + lastUpdateTime: "2026-09-12T20:40:01Z" + message: ReplicaSet "mariadb-6fcf465cc4" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"bookstack-config-pvc","namespace":"bookstack-db"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-12T20:44:04Z" + finalizers: + - kubernetes.io/pvc-protection + name: bookstack-config-pvc + namespace: bookstack-db + resourceVersion: "835762" + uid: b9ed0926-9879-4ddd-885e-0948cd48ce3f + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-b9ed0926-9879-4ddd-885e-0948cd48ce3f + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + phase: Bound +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"mariadb-data-pvc","namespace":"bookstack-db"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-12T20:39:27Z" + finalizers: + - kubernetes.io/pvc-protection + name: mariadb-data-pvc + namespace: bookstack-db + resourceVersion: "834328" + uid: b87615cd-7e29-4522-995d-4e2cd1144a0a + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-b87615cd-7e29-4522-995d-4e2cd1144a0a + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + phase: Bound +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"bookstack-web-service","namespace":"bookstack-db"},"spec":{"ports":[{"port":80,"targetPort":80}],"selector":{"app":"bookstack-web"}}} + creationTimestamp: "2026-09-12T20:44:04Z" + name: bookstack-web-service + namespace: bookstack-db + resourceVersion: "835699" + uid: 7df0e394-c2d0-482a-8212-be0a74bf20af + spec: + clusterIP: 10.43.136.41 + clusterIPs: + - 10.43.136.41 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 80 + protocol: TCP + targetPort: 80 + selector: + app: bookstack-web + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"mariadb-service","namespace":"bookstack-db"},"spec":{"ports":[{"port":3306}],"selector":{"app":"mariadb"}}} + creationTimestamp: "2026-09-12T20:39:27Z" + name: mariadb-service + namespace: bookstack-db + resourceVersion: "834266" + uid: 3ec5d3ac-c8a8-474a-a21a-2db1ced6c503 + spec: + clusterIP: 10.43.161.65 + clusterIPs: + - 10.43.161.65 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 3306 + protocol: TCP + targetPort: 3306 + selector: + app: mariadb + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +kind: List +metadata: + resourceVersion: "" diff --git a/bookstack/ingress.yaml b/bookstack/ingress.yaml new file mode 100644 index 0000000..26bca50 --- /dev/null +++ b/bookstack/ingress.yaml @@ -0,0 +1,29 @@ +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: bookstack-local-ingress + namespace: bookstack-db + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX how to bridge the internal pod connection protocol + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + # AUTOMATED TLS FIX: Instructs NGINX where to mount the generated certificate + tls: + - secretName: bookstack-tls-secret + hosts: + - wiki.needlestack.boo + rules: + - host: wiki.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: bookstack-web-service + port: + number: 80 diff --git a/bookstack/resources.yaml b/bookstack/resources.yaml new file mode 100644 index 0000000..7ea0d1b --- /dev/null +++ b/bookstack/resources.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +items: +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-12T20:23:24Z" + finalizers: + - kubernetes.io/pvc-protection + labels: + app.kubernetes.io/component: primary + app.kubernetes.io/instance: bookstack + app.kubernetes.io/name: mariadb + app.kubernetes.io/part-of: mariadb + name: data-bookstack-mariadb-0 + namespace: bookstack + resourceVersion: "829155" + uid: 4faa1a16-2c11-4c8b-9c09-5d271c39ebf2 + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-4faa1a16-2c11-4c8b-9c09-5d271c39ebf2 + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + phase: Bound +kind: List +metadata: + resourceVersion: "" diff --git a/cloudflared/cloudflared-backup.yaml b/cloudflared/cloudflared-backup.yaml new file mode 100644 index 0000000..fbf2abc --- /dev/null +++ b/cloudflared/cloudflared-backup.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "4" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"cloudflared","namespace":"cloudflare"},"spec":{"replicas":2,"selector":{"matchLabels":{"app":"cloudflared"}},"template":{"metadata":{"labels":{"app":"cloudflared"}},"spec":{"containers":[{"args":["tunnel","--no-autoupdate","run"],"env":[{"name":"TUNNEL_TOKEN","value":"eyJhIjoiZGQzZGM4NzUyNWFlM2VhMjg4MjljY2QzN2IzM2IwNDUiLCJ0IjoiZjVkMzkwMmMtMWIzNS00MDg1LTk1MDctYTIxZGE0Njg0NzkzIiwicyI6Ik1tTmhZems0Tm1NdFkyUmhOQzAwTkdFMExUazFNV010TldRM00yWXhaalV4TlRJeiJ9"}],"image":"cloudflare/cloudflared:latest","name":"cloudflared"}],"dnsConfig":{"nameservers":["1.1.1.1","8.8.8.8"]},"dnsPolicy":"None"}}}} + creationTimestamp: "2026-09-12T15:12:46Z" + generation: 4 + name: cloudflared + namespace: cloudflare + resourceVersion: "5577092" + uid: ded6d529-b685-4829-a7f6-7d16dcbf8598 +spec: + progressDeadlineSeconds: 600 + replicas: 2 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: cloudflared + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2026-09-24T20:27:50Z" + creationTimestamp: null + labels: + app: cloudflared + spec: + containers: + - args: + - tunnel + - --no-autoupdate + - run + env: + - name: TUNNEL_TOKEN + value: eyJhIjoiZGQzZGM4NzUyNWFlM2VhMjg4MjljY2QzN2IzM2IwNDUiLCJ0IjoiZjVkMzkwMmMtMWIzNS00MDg1LTk1MDctYTIxZGE0Njg0NzkzIiwicyI6Ik1tTmhZems0Tm1NdFkyUmhOQzAwTkdFMExUazFNV010TldRM00yWXhaalV4TlRJeiJ9 + image: cloudflare/cloudflared:latest + imagePullPolicy: Always + name: cloudflared + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsConfig: + nameservers: + - 10.43.0.10 + searches: + - svc.cluster.local + - cluster.local + dnsPolicy: None + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +status: + availableReplicas: 2 + conditions: + - lastTransitionTime: "2026-09-12T15:18:53Z" + lastUpdateTime: "2026-09-12T15:18:53Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: "2026-09-12T15:12:46Z" + lastUpdateTime: "2026-09-24T20:27:55Z" + message: ReplicaSet "cloudflared-6b4f8bccfc" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + observedGeneration: 4 + readyReplicas: 2 + replicas: 2 + updatedReplicas: 2 diff --git a/cobalt/resources.yaml b/cobalt/resources.yaml new file mode 100644 index 0000000..ac52d9c --- /dev/null +++ b/cobalt/resources.yaml @@ -0,0 +1,150 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: cobalt-downloader +--- +# 1. THE BACKEND API (Processing Engine) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cobalt-api + namespace: cobalt-downloader + labels: + app: cobalt-api +spec: + replicas: 1 + selector: + matchLabels: + app: cobalt-api + template: + metadata: + labels: + app: cobalt-api + spec: + containers: + - name: api + image: ghcr.io/imputnet/cobalt:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9000 + name: http-api + env: + # Identifies its own public API endpoint location + - name: API_URL + value: "https://cobalt-api.needlestack.boo" + # Grants cross-origin clearance strictly to your web UI subdomain + - name: CORS_ALLOWED_ORIGINS + value: "https://cobalt.needlestack.boo" + resources: + limits: + cpu: "500m" + memory: "512Mi" + requests: + cpu: "100m" + memory: "128Mi" +--- +apiVersion: v1 +kind: Service +metadata: + name: cobalt-api-svc + namespace: cobalt-downloader +spec: + selector: + app: cobalt-api + ports: + - protocol: TCP + port: 9000 + targetPort: 9000 +--- +# 2. THE WEB FRONTEND UI (Browser Dashboard) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cobalt-web + namespace: cobalt-downloader + labels: + app: cobalt-web +spec: + replicas: 1 + selector: + matchLabels: + app: cobalt-web + template: + metadata: + labels: + app: cobalt-web + spec: + containers: + - name: web + # Swapped to a verified production container path that respects HTTPS parameters + image: ghcr.io/notnite/cobalt-web:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + name: http-web + env: + # This image expects API_URL to pass the backend routing location to browsers + - name: API_URL + value: "https://cobalt-api.needlestack.boo" + resources: + limits: + cpu: "500m" + memory: "512Mi" + requests: + cpu: "50m" + memory: "128Mi" +--- +apiVersion: v1 +kind: Service +metadata: + name: cobalt-web-svc + namespace: cobalt-downloader +spec: + selector: + app: cobalt-web + ports: + - protocol: TCP + port: 80 + targetPort: 80 +--- +# 3. NGINX INGRESS MAPPING (Dual Host Control) +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cobalt-ingress + namespace: cobalt-downloader + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/enable-cors: "true" +spec: + ingressClassName: nginx + tls: + - secretName: cobalt-tls-secret + hosts: + - cobalt.needlestack.boo + - cobalt-api.needlestack.boo + rules: + # Routes your primary browser landing screen traffic + - host: cobalt.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: cobalt-web-svc + port: + number: 80 + # Routes underlying stream data traffic + - host: cobalt-api.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: cobalt-api-svc + port: + number: 9000 diff --git a/convertx/values.yaml b/convertx/values.yaml new file mode 100644 index 0000000..108e2d1 --- /dev/null +++ b/convertx/values.yaml @@ -0,0 +1,82 @@ +controllers: + main: + type: deployment + strategy: Recreate + + containers: + main: + image: + repository: ghcr.io/c4illin/convertx + tag: latest + pullPolicy: IfNotPresent + + # RELAX RESOURCE LIMITS TO PREVENT THREAD STALLS + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: "6000m" # Allow full core utilization during deep video transcodes + memory: 4Gi # Slightly higher ceiling for large video processing blocks + + env: + PUID: "1000" + PGID: "1000" + JWT_SECRET: "d173a64678b199b21c0bc52ef17da56cec31f3eedc3685e68b394b7bc119e38b" + HTTP_ALLOWED: "true" + ALLOW_UNAUTHENTICATED: "false" + # APPLICATION LEVEL CONCURRENCY LIMIT + MAX_CONVERT_PROCESS: "1" # Restricts the app to 1 conversion at a time to prevent CPU crowding + + securityContext: + privileged: true + +service: + main: + controller: main + type: NodePort + ports: + http: + port: 3050 + targetPort: 3000 + nodePort: 32050 + +persistence: + data: + enabled: true + type: persistentVolumeClaim + storageClass: "longhorn" + accessMode: ReadWriteOnce + size: 10Gi + globalMounts: + - path: /app/data + igpu: + enabled: true + type: hostPath + hostPath: /dev/dri + globalMounts: + - path: /dev/dri +# values.yaml +ingress: + main: + enabled: true + className: nginx + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Bridges to the unencrypted pod port 3050 safely + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + hosts: + - host: convertx.needlestack.boo + paths: + - path: / + pathType: Prefix + service: + identifier: main + port: 3050 + # AUTOMATED TLS FIX: Instructs NGINX where to store the secret + tls: + - secretName: convertx-tls-secret + hosts: + - convertx.needlestack.boo + diff --git a/gitea/gitea-stack.yaml b/gitea/gitea-stack.yaml new file mode 100644 index 0000000..e774439 --- /dev/null +++ b/gitea/gitea-stack.yaml @@ -0,0 +1,188 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: gitea +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gitea-postgres-pvc + namespace: gitea +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gitea-app-pvc + namespace: gitea +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + storageClassName: longhorn +--- +apiVersion: v1 +kind: Service +metadata: + name: gitea-postgres-service + namespace: gitea +spec: + ports: + - port: 5432 + targetPort: 5432 + selector: + app: gitea-postgres + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + name: gitea-service + namespace: gitea +spec: + ports: + - name: http + port: 80 + targetPort: 3000 + - name: ssh + port: 2222 + targetPort: 2222 + selector: + app: gitea-app + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gitea-postgres + namespace: gitea +spec: + replicas: 1 + selector: + matchLabels: + app: gitea-postgres + template: + metadata: + labels: + app: gitea-postgres + spec: + dnsConfig: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsPolicy: None + containers: + - name: postgres + image: postgres:15-alpine + env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + - name: POSTGRES_USER + value: gitea_admin + - name: POSTGRES_PASSWORD + value: NeedlestackCodeVault2026 + - name: POSTGRES_DB + value: giteadb + ports: + - containerPort: 5432 + name: dbport + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: db-data + volumes: + - name: db-data + persistentVolumeClaim: + claimName: gitea-postgres-pvc +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gitea-app + namespace: gitea +spec: + replicas: 1 + selector: + matchLabels: + app: gitea-app + template: + metadata: + labels: + app: gitea-app + spec: + securityContext: # <-- ADD THIS SECTION + fsGroup: 1000 # <-- FORCES STORAGE VOLUMES TO BE OWNED BY USER 1000 + containers: + - name: gitea + image: gitea/gitea:1.21-rootless + env: + # Core Database Settings + - name: GITEA__database__DB_TYPE + value: postgres + - name: GITEA__database__HOST + value: gitea-postgres-service:5432 + - name: GITEA__database__NAME + value: giteadb + - name: GITEA__database__USER + value: gitea_admin + - name: GITEA__database__PASSWD + value: NeedlestackCodeVault2026 + + # Rootless Paths Fixes (Maps configuration to persistent /data) + - name: GITEA_APP_INI + value: /data/gitea/conf/app.ini + - name: GITEA_CUSTOM + value: /data/gitea + - name: GITEA_WORK_DIR + value: /data/gitea + + # The Security Fixes (Bypasses installer & disables public sign-ups) + - name: GITEA__security__INSTALL_LOCK + value: "true" + - name: GITEA__service__DISABLE_REGISTRATION + value: "true" + ports: + - containerPort: 3000 + name: http + - containerPort: 2222 + name: ssh + volumeMounts: + - mountPath: /data + name: gitea-data + volumes: + - name: gitea-data + persistentVolumeClaim: + claimName: gitea-app-pvc +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gitea-local-ingress + namespace: gitea + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + tls: + - secretName: gitea-tls-secret + hosts: + - git.needlestack.boo + rules: + - host: git.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gitea-service + port: + number: 80 diff --git a/gitea/ingress.yaml b/gitea/ingress.yaml new file mode 100644 index 0000000..547efce --- /dev/null +++ b/gitea/ingress.yaml @@ -0,0 +1,29 @@ +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gitea-local-ingress + namespace: gitea + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX to bridge to the unencrypted pod port 80 + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + # AUTOMATED TLS FIX: Instructs NGINX where to mount the generated certificate + tls: + - secretName: gitea-tls-secret + hosts: + - git.needlestack.boo + rules: + - host: git.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gitea-service + port: + number: 80 diff --git a/gitea/resources.yaml b/gitea/resources.yaml new file mode 100644 index 0000000..0c5ca8f --- /dev/null +++ b/gitea/resources.yaml @@ -0,0 +1,397 @@ +apiVersion: v1 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "3" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"gitea-app","namespace":"gitea"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"gitea-app"}},"template":{"metadata":{"labels":{"app":"gitea-app"}},"spec":{"containers":[{"env":[{"name":"GITEA__database__DB_TYPE","value":"postgres"},{"name":"GITEA__database__HOST","value":"gitea-postgres-service:5432"},{"name":"GITEA__database__NAME","value":"giteadb"},{"name":"GITEA__database__USER","value":"gitea_admin"},{"name":"GITEA__database__PASSWD","value":"NeedlestackCodeVault2026"}],"image":"gitea/gitea:1.21-rootless","name":"gitea","ports":[{"containerPort":3000,"name":"http"},{"containerPort":2222,"name":"ssh"}],"volumeMounts":[{"mountPath":"/data","name":"gitea-data"}]}],"volumes":[{"name":"gitea-data","persistentVolumeClaim":{"claimName":"gitea-app-pvc"}}]}}}} + creationTimestamp: "2026-09-21T21:51:38Z" + generation: 5 + name: gitea-app + namespace: gitea + resourceVersion: "4415267" + uid: 46e7fffd-bd04-4e55-a82a-001fb7f459f6 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: gitea-app + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2026-09-21T22:09:01Z" + creationTimestamp: null + labels: + app: gitea-app + spec: + containers: + - env: + - name: GITEA__database__DB_TYPE + value: postgres + - name: GITEA__database__HOST + value: gitea-postgres-service:5432 + - name: GITEA__database__NAME + value: giteadb + - name: GITEA__database__USER + value: gitea_admin + - name: GITEA__database__PASSWD + value: NeedlestackCodeVault2026 + image: gitea/gitea:1.21-rootless + imagePullPolicy: IfNotPresent + name: gitea + ports: + - containerPort: 3000 + name: http + protocol: TCP + - containerPort: 2222 + name: ssh + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /data + name: gitea-data + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: gitea-data + persistentVolumeClaim: + claimName: gitea-app-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-21T21:51:38Z" + lastUpdateTime: "2026-09-21T22:11:07Z" + message: ReplicaSet "gitea-app-644f4c9577" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + - lastTransitionTime: "2026-09-21T22:11:36Z" + lastUpdateTime: "2026-09-21T22:11:36Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + observedGeneration: 5 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "2" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"gitea-postgres","namespace":"gitea"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"gitea-postgres"}},"template":{"metadata":{"labels":{"app":"gitea-postgres"}},"spec":{"containers":[{"env":[{"name":"PGDATA","value":"/var/lib/postgresql/data/pgdata"},{"name":"POSTGRES_USER","value":"gitea_admin"},{"name":"POSTGRES_PASSWORD","value":"NeedlestackCodeVault2026"},{"name":"POSTGRES_DB","value":"giteadb"}],"image":"postgres:15-alpine","name":"postgres","ports":[{"containerPort":5432,"name":"dbport"}],"volumeMounts":[{"mountPath":"/var/lib/postgresql/data","name":"db-data"}]}],"dnsConfig":{"nameservers":["1.1.1.1","8.8.8.8"]},"dnsPolicy":"None","volumes":[{"name":"db-data","persistentVolumeClaim":{"claimName":"gitea-postgres-pvc"}}]}}}} + creationTimestamp: "2026-09-21T21:51:14Z" + generation: 2 + name: gitea-postgres + namespace: gitea + resourceVersion: "4410146" + uid: df85a488-1a17-46f3-ae75-e7911f61d110 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: gitea-postgres + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: gitea-postgres + spec: + containers: + - env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + - name: POSTGRES_USER + value: gitea_admin + - name: POSTGRES_PASSWORD + value: NeedlestackCodeVault2026 + - name: POSTGRES_DB + value: giteadb + image: postgres:15-alpine + imagePullPolicy: IfNotPresent + name: postgres + ports: + - containerPort: 5432 + name: dbport + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: db-data + dnsConfig: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsPolicy: None + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: db-data + persistentVolumeClaim: + claimName: gitea-postgres-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-21T21:53:43Z" + lastUpdateTime: "2026-09-21T21:53:43Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: "2026-09-21T21:51:14Z" + lastUpdateTime: "2026-09-21T21:53:43Z" + message: ReplicaSet "gitea-postgres-5d7986b4b8" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + observedGeneration: 2 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"gitea-app-pvc","namespace":"gitea"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"20Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-21T21:51:38Z" + finalizers: + - kubernetes.io/pvc-protection + name: gitea-app-pvc + namespace: gitea + resourceVersion: "4409403" + uid: 509d3566-6a32-47e9-94f9-c9dc66cd00f3 + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-509d3566-6a32-47e9-94f9-c9dc66cd00f3 + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 20Gi + phase: Bound +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"gitea-postgres-pvc","namespace":"gitea"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"10Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-21T21:51:14Z" + finalizers: + - kubernetes.io/pvc-protection + name: gitea-postgres-pvc + namespace: gitea + resourceVersion: "4409170" + uid: 5da54bab-fc4d-4633-80bc-959c00115f46 + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-5da54bab-fc4d-4633-80bc-959c00115f46 + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + phase: Bound +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"gitea-postgres-service","namespace":"gitea"},"spec":{"ports":[{"port":5432,"targetPort":5432}],"selector":{"app":"gitea-postgres"}}} + creationTimestamp: "2026-09-21T21:51:14Z" + name: gitea-postgres-service + namespace: gitea + resourceVersion: "4409101" + uid: 826733f9-3f03-4289-921b-94f283f9e360 + spec: + clusterIP: 10.43.238.230 + clusterIPs: + - 10.43.238.230 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 5432 + protocol: TCP + targetPort: 5432 + selector: + app: gitea-postgres + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"gitea-service","namespace":"gitea"},"spec":{"ports":[{"name":"http","port":80,"targetPort":3000},{"name":"ssh","port":2222,"targetPort":2222}],"selector":{"app":"gitea-app"}}} + creationTimestamp: "2026-09-21T21:51:38Z" + name: gitea-service + namespace: gitea + resourceVersion: "4409344" + uid: e6ca70bd-1eeb-48e7-b800-6193a15fcb76 + spec: + clusterIP: 10.43.6.10 + clusterIPs: + - 10.43.6.10 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: http + port: 80 + protocol: TCP + targetPort: 3000 + - name: ssh + port: 2222 + protocol: TCP + targetPort: 2222 + selector: + app: gitea-app + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: batch/v1 + kind: CronJob + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"batch/v1","kind":"CronJob","metadata":{"annotations":{},"name":"cluster-yaml-backup","namespace":"gitea"},"spec":{"concurrencyPolicy":"Forbid","jobTemplate":{"spec":{"template":{"spec":{"containers":[{"command":["/bin/sh","-c","echo \"=== Starting Automated Cluster Backup ===\"\n\n# Configure local git repository tracking parameters\ngit config --global user.name \"Cluster Backup Engine\"\ngit config --global user.email \"backup@needlestack.boo\"\ngit config --global safe.directory '*'\n\n# Constructing authorized credentials paths using the 4-part internal IP\nTARGET_IP=\"10.43.6.10\"\nCLONE_URL=\"http://${GITEA_USER}:${GITEA_TOKEN}@${TARGET_IP}/${GITEA_USER}/${GITEA_REPO}.git\"\nPUSH_URL=\"http://${GITEA_USER}:${GITEA_TOKEN}@${TARGET_IP}/${GITEA_USER}/${GITEA_REPO}.git\"\n\n# Safely download the private repository shell using the injected token keys\ngit clone ${CLONE_URL} /tmp/backup\ncd /tmp/backup\n\n# Refresh backup layout data paths\nrm -rf manifests/\nmkdir -p manifests/\n\n# Extract clean YAML resource records across the namespaces\nfor ns in open-webui homepage mumble adguard; do\n echo \"Backing up namespace: ${ns}\"\n mkdir -p \"manifests/${ns}\"\n kubectl get deployment,pvc,svc,ingress,cronjob,namespace -n ${ns} -o yaml \u003e \"manifests/${ns}/resources.yaml\" 2\u003e/dev/null\ndone\n\n# Sync the layout changes back into the private vault logs\ngit add .\nif ! git diff-index --quiet HEAD --; then\n git commit -m \"Automated backup: $(date +'%Y-%m-%d %H:%M:%S')\"\n git push ${PUSH_URL} main\n echo \"=== Backup Pushed Successfully ===\"\nelse\n echo \"=== No changes detected. Skipping push ===\"\nfi\n"],"env":[{"name":"GITEA_USER","value":"needlestack"},{"name":"GITEA_REPO","value":"cluster-manifests"},{"name":"GITEA_TOKEN","value":"4a403fa7615feea2cbeb0c1f98fcacc48b84d95a"}],"image":"alpine/k8s:1.28.2","name":"backup-worker"}],"restartPolicy":"OnFailure","serviceAccountName":"default"}}}},"schedule":"0 0 * * *"}} + creationTimestamp: "2026-09-21T22:38:26Z" + generation: 10 + name: cluster-yaml-backup + namespace: gitea + resourceVersion: "7914165" + uid: 8fde5768-6601-451d-885c-b919d3c9ae00 + spec: + concurrencyPolicy: Forbid + failedJobsHistoryLimit: 1 + jobTemplate: + metadata: + creationTimestamp: null + spec: + template: + metadata: + creationTimestamp: null + spec: + containers: + - command: + - /bin/sh + - -c + - | + echo "=== Starting Automated Cluster Backup ===" + + # Configure local git repository tracking parameters + git config --global user.name "Cluster Backup Engine" + git config --global user.email "backup@needlestack.boo" + git config --global safe.directory '*' + + # Constructing authorized credentials paths using the 4-part internal IP + TARGET_IP="10.43.6.10" + CLONE_URL="http://${GITEA_USER}:${GITEA_TOKEN}@${TARGET_IP}/${GITEA_USER}/${GITEA_REPO}.git" + PUSH_URL="http://${GITEA_USER}:${GITEA_TOKEN}@${TARGET_IP}/${GITEA_USER}/${GITEA_REPO}.git" + + # Safely download the private repository shell using the injected token keys + git clone ${CLONE_URL} /tmp/backup + cd /tmp/backup + + # Refresh backup layout data paths + rm -rf manifests/ + mkdir -p manifests/ + + # Extract clean YAML resource records across the namespaces + for ns in open-webui homepage mumble adguard; do + echo "Backing up namespace: ${ns}" + mkdir -p "manifests/${ns}" + kubectl get deployment,pvc,svc,ingress,cronjob,namespace -n ${ns} -o yaml > "manifests/${ns}/resources.yaml" 2>/dev/null + done + + # Sync the layout changes back into the private vault logs + git add . + if ! git diff-index --quiet HEAD --; then + git commit -m "Automated backup: $(date +'%Y-%m-%d %H:%M:%S')" + git push ${PUSH_URL} main + echo "=== Backup Pushed Successfully ===" + else + echo "=== No changes detected. Skipping push ===" + fi + env: + - name: GITEA_USER + value: needlestack + - name: GITEA_REPO + value: cluster-manifests + - name: GITEA_TOKEN + value: 4a403fa7615feea2cbeb0c1f98fcacc48b84d95a + image: alpine/k8s:1.28.2 + imagePullPolicy: Always + name: backup-worker + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: OnFailure + schedulerName: default-scheduler + securityContext: {} + serviceAccount: default + serviceAccountName: default + terminationGracePeriodSeconds: 30 + schedule: 0 0 * * * + successfulJobsHistoryLimit: 3 + suspend: false + status: + lastScheduleTime: "2026-09-30T00:00:00Z" + lastSuccessfulTime: "2026-09-30T00:00:05Z" +kind: List +metadata: + resourceVersion: "" diff --git a/homepage/homepage.yaml b/homepage/homepage.yaml new file mode 100644 index 0000000..e8d3395 --- /dev/null +++ b/homepage/homepage.yaml @@ -0,0 +1,99 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: homepage + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: homepage-config-pvc + namespace: homepage +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: longhorn + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: homepage + namespace: homepage +spec: + replicas: 1 + strategy: + type: RollingUpdate + selector: + matchLabels: + app: homepage + template: + metadata: + labels: + app: homepage + spec: + containers: + - name: homepage + image: ghcr.io/gethomepage/homepage:latest + imagePullPolicy: Always + ports: + - containerPort: 3000 + name: http + protocol: TCP + env: + - name: HOMEPAGE_ALLOWED_HOSTS + value: needlestack.boo,localhost:3000 + volumeMounts: + - mountPath: /app/config + name: config-volume + volumes: + - name: config-volume + persistentVolumeClaim: + claimName: homepage-config-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: homepage-service + namespace: homepage +spec: + type: ClusterIP + selector: + app: homepage + ports: + - port: 80 + protocol: TCP + targetPort: 3000 + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: homepage-local-ingress + namespace: homepage + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX to bridge to the unencrypted pod port safely + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + tls: + - secretName: homepage-tls-secret + hosts: + - needlestack.boo + rules: + - host: needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: homepage-service + port: + number: 80 diff --git a/homepage/ingress.yaml b/homepage/ingress.yaml new file mode 100644 index 0000000..151c7f9 --- /dev/null +++ b/homepage/ingress.yaml @@ -0,0 +1,29 @@ +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: homepage-local-ingress + namespace: homepage + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX how to bridge the internal pod connection protocol + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + # AUTOMATED TLS FIX: Instructs NGINX where to mount the generated certificate + tls: + - secretName: homepage-tls-secret + hosts: + - needlestack.boo + rules: + - host: needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: homepage-service + port: + number: 80 diff --git a/homepage/resources.yaml b/homepage/resources.yaml new file mode 100644 index 0000000..4bba155 --- /dev/null +++ b/homepage/resources.yaml @@ -0,0 +1,430 @@ +apiVersion: v1 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "3" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"homepage","namespace":"homepage"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"homepage"}},"template":{"metadata":{"labels":{"app":"homepage"}},"spec":{"containers":[{"env":[{"name":"HOMEPAGE_ALLOWED_HOSTS","value":"needlestack.boo,localhost:3000"}],"image":"ghcr.io/gethomepage/homepage:latest","name":"homepage","ports":[{"containerPort":3000,"name":"http"}],"volumeMounts":[{"mountPath":"/app/config","name":"config-volume"}]}],"dnsConfig":{"nameservers":["1.1.1.1","8.8.8.8"]},"dnsPolicy":"None","volumes":[{"name":"config-volume","persistentVolumeClaim":{"claimName":"homepage-config-pvc"}}]}}}} + creationTimestamp: "2026-09-18T22:22:12Z" + generation: 9 + name: homepage + namespace: homepage + resourceVersion: "3247538" + uid: 30d404a3-fcc7-47df-bb80-e1dbb346e606 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: homepage + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "2026-09-18T22:31:21Z" + creationTimestamp: null + labels: + app: homepage + spec: + containers: + - env: + - name: HOMEPAGE_ALLOWED_HOSTS + value: needlestack.boo,localhost:3000 + image: ghcr.io/gethomepage/homepage:latest + imagePullPolicy: Always + name: homepage + ports: + - containerPort: 3000 + name: http + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app/config + name: config-volume + dnsConfig: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsPolicy: None + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: config-volume + persistentVolumeClaim: + claimName: homepage-config-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-18T22:22:12Z" + lastUpdateTime: "2026-09-18T22:37:08Z" + message: ReplicaSet "homepage-58584558c7" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + - lastTransitionTime: "2026-09-18T23:14:50Z" + lastUpdateTime: "2026-09-18T23:14:50Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + observedGeneration: 9 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"homepage-config-pvc","namespace":"homepage"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"5Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-18T22:22:12Z" + finalizers: + - kubernetes.io/pvc-protection + name: homepage-config-pvc + namespace: homepage + resourceVersion: "3232764" + uid: 2cd12c3c-0f79-46b5-94c6-c61d96a0b57a + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-2cd12c3c-0f79-46b5-94c6-c61d96a0b57a + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 5Gi + phase: Bound +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"homepage-service","namespace":"homepage"},"spec":{"ports":[{"port":80,"targetPort":3000}],"selector":{"app":"homepage"}}} + creationTimestamp: "2026-09-18T22:22:12Z" + name: homepage-service + namespace: homepage + resourceVersion: "3232702" + uid: 6c1eeb77-f148-40eb-be19-e567aaba7743 + spec: + clusterIP: 10.43.18.81 + clusterIPs: + - 10.43.18.81 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 80 + protocol: TCP + targetPort: 3000 + selector: + app: homepage + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T21:33:05Z" + labels: + kubernetes.io/metadata.name: adguard + name: adguard + name: adguard + resourceVersion: "41653" + uid: 6d812d6a-1921-4169-9805-7d1192073f46 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-12T20:23:24Z" + labels: + kubernetes.io/metadata.name: bookstack + name: bookstack + name: bookstack + resourceVersion: "829004" + uid: e5c59011-b6b6-4f53-a655-a140b7363443 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"bookstack-db"}} + creationTimestamp: "2026-09-12T20:39:27Z" + labels: + kubernetes.io/metadata.name: bookstack-db + name: bookstack-db + resourceVersion: "834254" + uid: 9600ab15-9b1a-451b-af4e-13ff3b672331 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T22:46:31Z" + labels: + kubernetes.io/metadata.name: cert-manager + name: cert-manager + name: cert-manager + resourceVersion: "61514" + uid: 05b40520-6a11-49af-96df-3d3289a9ed8f + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"cloudflare"}} + creationTimestamp: "2026-09-12T15:12:46Z" + labels: + kubernetes.io/metadata.name: cloudflare + name: cloudflare + resourceVersion: "737700" + uid: f294d89c-0348-444a-b2a8-57303311e45e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: default + name: default + resourceVersion: "266" + uid: ae7a3d3e-3d08-43fe-8e2c-8f72ef066b9e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"gitea"}} + creationTimestamp: "2026-09-21T21:51:14Z" + labels: + kubernetes.io/metadata.name: gitea + name: gitea + resourceVersion: "4409092" + uid: 6398ba34-919b-4392-afbf-ef0054176a1b + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"homepage"}} + creationTimestamp: "2026-09-18T22:22:12Z" + labels: + kubernetes.io/metadata.name: homepage + name: homepage + resourceVersion: "3232692" + uid: eaafb62a-9a9a-45f3-a7b4-7fa4d1e2ae6b + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:46:49Z" + labels: + kubernetes.io/metadata.name: ingress-nginx + name: ingress-nginx + name: ingress-nginx + resourceVersion: "12923" + uid: 46118181-4db6-4c3c-9fdc-f8cc20f28323 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"intel-device-plugin"}} + creationTimestamp: "2026-09-17T20:41:41Z" + labels: + kubernetes.io/metadata.name: intel-device-plugin + name: intel-device-plugin + resourceVersion: "2809324" + uid: b132d304-80cb-4bdc-88cd-2073a8239312 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-node-lease + name: kube-node-lease + resourceVersion: "267" + uid: 81e945a2-a9f5-4ddc-83a8-2bc214a1559e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-public + name: kube-public + resourceVersion: "268" + uid: 2142a69f-f0bd-45af-8738-563df7f99012 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-system + name: kube-system + resourceVersion: "269" + uid: 1b18f0bc-7131-46b4-9e4b-bb0af3d7d63e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:35:47Z" + labels: + kubernetes.io/metadata.name: longhorn-system + name: longhorn-system + name: longhorn-system + resourceVersion: "9213" + uid: abd04167-9f4b-468e-a637-1649efff2bc7 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"mumble"}} + creationTimestamp: "2026-09-19T18:14:35Z" + labels: + kubernetes.io/metadata.name: mumble + name: mumble + resourceVersion: "3560003" + uid: cc88370b-9275-43f6-a1f9-2bb663600c48 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"nordvpn"}} + creationTimestamp: "2026-09-11T20:23:38Z" + labels: + kubernetes.io/metadata.name: nordvpn + name: nordvpn + resourceVersion: "417127" + uid: 15f7c8a5-1b18-475f-84dc-90e5963814fa + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"open-webui"}} + creationTimestamp: "2026-09-17T21:09:55Z" + labels: + kubernetes.io/metadata.name: open-webui + name: open-webui + resourceVersion: "2817739" + uid: 74bf3bef-51bc-405e-8f79-ebc9f52281c6 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:56:57Z" + labels: + kubernetes.io/metadata.name: portainer + name: portainer + name: portainer + resourceVersion: "16301" + uid: 17802103-8a9c-4b8f-aa42-03a0db58d2ae + spec: + finalizers: + - kubernetes + status: + phase: Active +kind: List +metadata: + resourceVersion: "" diff --git a/immich/ingress.yaml b/immich/ingress.yaml new file mode 100644 index 0000000..a5e2e14 --- /dev/null +++ b/immich/ingress.yaml @@ -0,0 +1,33 @@ +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: immich-local-ingress + namespace: immich + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX to bridge to the unencrypted pod port 2283 + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + # THE UPLOAD FIX: Disables NGINX's default 1MB file size boundary block + nginx.ingress.kubernetes.io/proxy-body-size: "0" + # Optional but highly recommended: Stops NGINX from buffering heavy media uploads to local node drives first + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" +spec: + ingressClassName: nginx + # AUTOMATED TLS FIX: Instructs NGINX where to mount the generated certificate + tls: + - secretName: immich-tls-secret + hosts: + - photos.needlestack.boo + rules: + - host: photos.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: immich-server + port: + number: 2283 diff --git a/immich/values.yaml b/immich/values.yaml new file mode 100644 index 0000000..53b9478 --- /dev/null +++ b/immich/values.yaml @@ -0,0 +1,36 @@ +controllers: + main: + containers: + main: + env: + DB_HOSTNAME: "immich-db-rw.immich.svc.cluster.local" + DB_DATABASE_NAME: "app" + DB_USERNAME: "app" + DB_PASSWORD: "OJPYu0ilCbLAu7bqG2ydDbjCdl7DHHKapKoB89aiZ64UJwFnMJZ0t3ZPmtVbjVel" + DB_PORT: "5432" + REDIS_HOSTNAME: "immich-valkey" + IMMICH_MACHINE_LEARNING_URL: "http://immich-machine-learning.immich.svc.cluster.local:2283" + securityContext: + privileged: true +immich: + persistence: + library: + existingClaim: "immich-photo-library-pvc" +valkey: + enabled: true +service: + main: + type: NodePort + ports: + http: + port: 2283 +persistence: + devdri: + enabled: true + type: hostPath + hostPath: /dev/dri + globalMounts: + - path: /dev/dri +ingress: + main: + enabled: false \ No newline at end of file diff --git a/kube-prometheus-stack/values.yaml b/kube-prometheus-stack/values.yaml new file mode 100644 index 0000000..95482d0 --- /dev/null +++ b/kube-prometheus-stack/values.yaml @@ -0,0 +1,42 @@ +# monitoring-values.yaml + +prometheus: + prometheusSpec: + # Forces Prometheus to find ServiceMonitors cluster-wide (like blocky in kube-system) + serviceMonitorSelectorNilUsesHelmValues: false + + ingress: + enabled: true + ingressClassName: nginx + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX to bridge to the unencrypted pod port safely + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + hosts: + - prometheus.needlestack.boo + paths: + - / + pathType: Prefix + tls: + - secretName: prometheus-tls-secret + hosts: + - prometheus.needlestack.boo + +grafana: + ingress: + enabled: true + ingressClassName: nginx + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX to bridge to the unencrypted pod port safely + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + hosts: + - grafana.needlestack.boo + # THE SCHEMA FIX: Wrapped as an explicit array object to match the chart constraints + paths: + - / + pathType: Prefix + tls: + - secretName: grafana-tls-secret + hosts: + - grafana.needlestack.boo diff --git a/mumble/resources.yaml b/mumble/resources.yaml new file mode 100644 index 0000000..da6300e --- /dev/null +++ b/mumble/resources.yaml @@ -0,0 +1,432 @@ +apiVersion: v1 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "1" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"mumble","namespace":"mumble"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"mumble"}},"template":{"metadata":{"labels":{"app":"mumble"}},"spec":{"containers":[{"image":"mumblevoip/mumble-server:latest","name":"murmur","ports":[{"containerPort":64738,"name":"mumble-tcp","protocol":"TCP"},{"containerPort":64738,"name":"mumble-udp","protocol":"UDP"}],"volumeMounts":[{"mountPath":"/data","name":"mumble-storage"}]}],"volumes":[{"name":"mumble-storage","persistentVolumeClaim":{"claimName":"mumble-data-pvc"}}]}}}} + creationTimestamp: "2026-09-19T18:14:35Z" + generation: 1 + name: mumble + namespace: mumble + resourceVersion: "3560270" + uid: 04f642f1-3525-4db7-88fe-4b09259c6264 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: mumble + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: mumble + spec: + containers: + - image: mumblevoip/mumble-server:latest + imagePullPolicy: Always + name: murmur + ports: + - containerPort: 64738 + name: mumble-tcp + protocol: TCP + - containerPort: 64738 + name: mumble-udp + protocol: UDP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /data + name: mumble-storage + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: mumble-storage + persistentVolumeClaim: + claimName: mumble-data-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-19T18:15:06Z" + lastUpdateTime: "2026-09-19T18:15:06Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: "2026-09-19T18:14:35Z" + lastUpdateTime: "2026-09-19T18:15:06Z" + message: ReplicaSet "mumble-757c5c899d" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"mumble-data-pvc","namespace":"mumble"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"2Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-19T18:14:35Z" + finalizers: + - kubernetes.io/pvc-protection + name: mumble-data-pvc + namespace: mumble + resourceVersion: "3560074" + uid: 85fb334b-6399-4887-917c-09e703d2a0d5 + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-85fb334b-6399-4887-917c-09e703d2a0d5 + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 2Gi + phase: Bound +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"mumble-service","namespace":"mumble"},"spec":{"ports":[{"name":"mumble-tcp","nodePort":31738,"port":64738,"protocol":"TCP","targetPort":64738},{"name":"mumble-udp","nodePort":31738,"port":64738,"protocol":"UDP","targetPort":64738}],"selector":{"app":"mumble"},"type":"NodePort"}} + creationTimestamp: "2026-09-19T18:14:35Z" + name: mumble-service + namespace: mumble + resourceVersion: "3560016" + uid: c20b0b3a-5d1d-45e4-aec4-37659797bb49 + spec: + clusterIP: 10.43.235.20 + clusterIPs: + - 10.43.235.20 + externalTrafficPolicy: Cluster + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - name: mumble-tcp + nodePort: 31738 + port: 64738 + protocol: TCP + targetPort: 64738 + - name: mumble-udp + nodePort: 31738 + port: 64738 + protocol: UDP + targetPort: 64738 + selector: + app: mumble + sessionAffinity: None + type: NodePort + status: + loadBalancer: {} +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T21:33:05Z" + labels: + kubernetes.io/metadata.name: adguard + name: adguard + name: adguard + resourceVersion: "41653" + uid: 6d812d6a-1921-4169-9805-7d1192073f46 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-12T20:23:24Z" + labels: + kubernetes.io/metadata.name: bookstack + name: bookstack + name: bookstack + resourceVersion: "829004" + uid: e5c59011-b6b6-4f53-a655-a140b7363443 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"bookstack-db"}} + creationTimestamp: "2026-09-12T20:39:27Z" + labels: + kubernetes.io/metadata.name: bookstack-db + name: bookstack-db + resourceVersion: "834254" + uid: 9600ab15-9b1a-451b-af4e-13ff3b672331 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T22:46:31Z" + labels: + kubernetes.io/metadata.name: cert-manager + name: cert-manager + name: cert-manager + resourceVersion: "61514" + uid: 05b40520-6a11-49af-96df-3d3289a9ed8f + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"cloudflare"}} + creationTimestamp: "2026-09-12T15:12:46Z" + labels: + kubernetes.io/metadata.name: cloudflare + name: cloudflare + resourceVersion: "737700" + uid: f294d89c-0348-444a-b2a8-57303311e45e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: default + name: default + resourceVersion: "266" + uid: ae7a3d3e-3d08-43fe-8e2c-8f72ef066b9e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"gitea"}} + creationTimestamp: "2026-09-21T21:51:14Z" + labels: + kubernetes.io/metadata.name: gitea + name: gitea + resourceVersion: "4409092" + uid: 6398ba34-919b-4392-afbf-ef0054176a1b + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"homepage"}} + creationTimestamp: "2026-09-18T22:22:12Z" + labels: + kubernetes.io/metadata.name: homepage + name: homepage + resourceVersion: "3232692" + uid: eaafb62a-9a9a-45f3-a7b4-7fa4d1e2ae6b + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:46:49Z" + labels: + kubernetes.io/metadata.name: ingress-nginx + name: ingress-nginx + name: ingress-nginx + resourceVersion: "12923" + uid: 46118181-4db6-4c3c-9fdc-f8cc20f28323 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"intel-device-plugin"}} + creationTimestamp: "2026-09-17T20:41:41Z" + labels: + kubernetes.io/metadata.name: intel-device-plugin + name: intel-device-plugin + resourceVersion: "2809324" + uid: b132d304-80cb-4bdc-88cd-2073a8239312 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-node-lease + name: kube-node-lease + resourceVersion: "267" + uid: 81e945a2-a9f5-4ddc-83a8-2bc214a1559e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-public + name: kube-public + resourceVersion: "268" + uid: 2142a69f-f0bd-45af-8738-563df7f99012 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-system + name: kube-system + resourceVersion: "269" + uid: 1b18f0bc-7131-46b4-9e4b-bb0af3d7d63e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:35:47Z" + labels: + kubernetes.io/metadata.name: longhorn-system + name: longhorn-system + name: longhorn-system + resourceVersion: "9213" + uid: abd04167-9f4b-468e-a637-1649efff2bc7 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"mumble"}} + creationTimestamp: "2026-09-19T18:14:35Z" + labels: + kubernetes.io/metadata.name: mumble + name: mumble + resourceVersion: "3560003" + uid: cc88370b-9275-43f6-a1f9-2bb663600c48 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"nordvpn"}} + creationTimestamp: "2026-09-11T20:23:38Z" + labels: + kubernetes.io/metadata.name: nordvpn + name: nordvpn + resourceVersion: "417127" + uid: 15f7c8a5-1b18-475f-84dc-90e5963814fa + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"open-webui"}} + creationTimestamp: "2026-09-17T21:09:55Z" + labels: + kubernetes.io/metadata.name: open-webui + name: open-webui + resourceVersion: "2817739" + uid: 74bf3bef-51bc-405e-8f79-ebc9f52281c6 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:56:57Z" + labels: + kubernetes.io/metadata.name: portainer + name: portainer + name: portainer + resourceVersion: "16301" + uid: 17802103-8a9c-4b8f-aa42-03a0db58d2ae + spec: + finalizers: + - kubernetes + status: + phase: Active +kind: List +metadata: + resourceVersion: "" diff --git a/open-webui/ingress.yaml b/open-webui/ingress.yaml new file mode 100644 index 0000000..5eb6884 --- /dev/null +++ b/open-webui/ingress.yaml @@ -0,0 +1,34 @@ +# ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: openwebui-local-ingress + namespace: open-webui + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Instructs NGINX to bridge to the unencrypted pod port 80 + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + + # THE STREAMING FIX: Forces NGINX to instantly forward tokens to your browser + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" +spec: + ingressClassName: nginx + # AUTOMATED TLS FIX: Instructs NGINX where to mount the generated certificate + tls: + - secretName: openwebui-tls-secret + hosts: + - ai.needlestack.boo + rules: + - host: ai.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: open-webui-service + port: + number: 80 diff --git a/open-webui/ollama-fallback.yaml b/open-webui/ollama-fallback.yaml new file mode 100644 index 0000000..8066691 --- /dev/null +++ b/open-webui/ollama-fallback.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ollama-fallback + namespace: open-webui +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: ollama-fallback + template: + metadata: + labels: + app: ollama-fallback + spec: + # Pin the pod strictly to Node 3 + nodeSelector: + kubernetes.io/hostname: nst-node-3 + + containers: + - name: ollama + # THE BOOT FIX: Using a frozen tag allows local caching, bypassing the 8-minute pull + image: ollama/ollama:0.5.4 + imagePullPolicy: IfNotPresent + # THE FIX: Injects environment variables to pin the model in RAM permanently + env: + - name: OLLAMA_KEEP_ALIVE + value: "-1" + # THE SPEED FIX: Forces the calculations to fit perfectly onto your hardware threads + - name: OLLAMA_NUM_THREADS + value: "4" + ports: + - containerPort: 11434 + name: ollama-http + resources: + # Matching limits and requests perfectly eliminates CPU scheduling wait loops + requests: + memory: "2Gi" + cpu: "4" # Guarantee 4 physical cores + limits: + memory: "4Gi" + cpu: "4" # Perfectly matches the 4 execution threads above + volumeMounts: + - name: local-model-storage + mountPath: /root/.ollama + + volumes: + # THE FIX: Bypasses Longhorn replication entirely + - name: local-model-storage + hostPath: + path: /var/lib/ollama-models + type: DirectoryOrCreate + +--- +apiVersion: v1 +kind: Service +metadata: + name: ollama-fallback-service + namespace: open-webui +spec: + selector: + app: ollama-fallback + ports: + - protocol: TCP + port: 11434 + targetPort: 11434 diff --git a/open-webui/open-webui-stack.yaml b/open-webui/open-webui-stack.yaml new file mode 100644 index 0000000..d96b996 --- /dev/null +++ b/open-webui/open-webui-stack.yaml @@ -0,0 +1,116 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: openwebui-db + namespace: open-webui +spec: + instances: 1 + imageName: ghcr.io/cloudnative-pg/postgresql:16 + bootstrap: + initdb: + database: openwebui + owner: webui_admin + storage: + size: 10Gi + storageClass: longhorn + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: open-webui + namespace: open-webui +spec: + replicas: 1 + revisionHistoryLimit: 5 + selector: + matchLabels: + app: open-webui + strategy: + type: Recreate + template: + metadata: + labels: + app: open-webui + spec: + containers: + - name: open-webui + image: ghcr.io/open-webui/open-webui:main + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: http + protocol: TCP + env: + # THE POSTGRES ROUTE: Connects your Python app directly to the CNPG cluster + - name: DATABASE_URL + value: "postgresql://webui_admin:$(password)@openwebui-db-rw:5432/openwebui" + # CORE CAPABILITIES: Kept completely stock to support your fallback and LM Studio + - name: ENABLE_OLLAMA_API + value: "True" + - name: ENABLE_OPENAI_API + value: "True" + - name: WEBUI_SECRET_KEY + value: "5e7fb9706cbd8137baf6299a6e6af6297ae03c6bfe4a6edb" + envFrom: + - secretRef: + name: openwebui-db-app + volumeMounts: + - mountPath: /app/backend/data + name: webui-data + resources: + requests: + memory: "1Gi" + cpu: "500m" + limits: + memory: "2Gi" + cpu: "2" + # THE FIXED DNS LOGIC: Allows the container to see internal cluster service maps natively + dnsPolicy: ClusterFirst + restartPolicy: Always + volumes: + - name: webui-data + persistentVolumeClaim: + claimName: open-webui-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: open-webui-service + namespace: open-webui +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 8080 + selector: + app: open-webui + type: ClusterIP + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: openwebui-local-ingress + namespace: open-webui + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + tls: + - secretName: openwebui-tls-secret + hosts: + - ai.needlestack.boo + rules: + - host: ai.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: open-webui-service + port: + number: 80 diff --git a/open-webui/resources.yaml b/open-webui/resources.yaml new file mode 100644 index 0000000..d82241f --- /dev/null +++ b/open-webui/resources.yaml @@ -0,0 +1,430 @@ +apiVersion: v1 +items: +- apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + deployment.kubernetes.io/revision: "1" + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"open-webui","namespace":"open-webui"},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"open-webui"}},"template":{"metadata":{"labels":{"app":"open-webui"}},"spec":{"containers":[{"env":[{"name":"ENABLE_OLLAMA_API","value":"False"},{"name":"ENABLE_OPENAI_API","value":"True"}],"image":"ghcr.io/open-webui/open-webui:main","name":"open-webui","ports":[{"containerPort":8080,"name":"http"}],"volumeMounts":[{"mountPath":"/app/backend/data","name":"webui-data"}]}],"dnsConfig":{"nameservers":["1.1.1.1","8.8.8.8"]},"dnsPolicy":"None","volumes":[{"name":"webui-data","persistentVolumeClaim":{"claimName":"open-webui-pvc"}}]}}}} + creationTimestamp: "2026-09-17T21:09:55Z" + generation: 1 + name: open-webui + namespace: open-webui + resourceVersion: "2817943" + uid: 507cd7bd-b252-4060-bf66-2259a42b1889 + spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: open-webui + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: open-webui + spec: + containers: + - env: + - name: ENABLE_OLLAMA_API + value: "False" + - name: ENABLE_OPENAI_API + value: "True" + image: ghcr.io/open-webui/open-webui:main + imagePullPolicy: IfNotPresent + name: open-webui + ports: + - containerPort: 8080 + name: http + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /app/backend/data + name: webui-data + dnsConfig: + nameservers: + - 1.1.1.1 + - 8.8.8.8 + dnsPolicy: None + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + volumes: + - name: webui-data + persistentVolumeClaim: + claimName: open-webui-pvc + status: + availableReplicas: 1 + conditions: + - lastTransitionTime: "2026-09-17T21:10:13Z" + lastUpdateTime: "2026-09-17T21:10:13Z" + message: Deployment has minimum availability. + reason: MinimumReplicasAvailable + status: "True" + type: Available + - lastTransitionTime: "2026-09-17T21:09:55Z" + lastUpdateTime: "2026-09-17T21:10:13Z" + message: ReplicaSet "open-webui-7d9f677bcf" has successfully progressed. + reason: NewReplicaSetAvailable + status: "True" + type: Progressing + observedGeneration: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +- apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"PersistentVolumeClaim","metadata":{"annotations":{},"name":"open-webui-pvc","namespace":"open-webui"},"spec":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"15Gi"}},"storageClassName":"longhorn"}} + pv.kubernetes.io/bind-completed: "yes" + pv.kubernetes.io/bound-by-controller: "yes" + volume.beta.kubernetes.io/storage-provisioner: driver.longhorn.io + volume.kubernetes.io/storage-provisioner: driver.longhorn.io + creationTimestamp: "2026-09-17T21:09:55Z" + finalizers: + - kubernetes.io/pvc-protection + name: open-webui-pvc + namespace: open-webui + resourceVersion: "2817816" + uid: 8dea60f9-fb9c-48b2-ae21-8a197033605a + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 15Gi + storageClassName: longhorn + volumeMode: Filesystem + volumeName: pvc-8dea60f9-fb9c-48b2-ae21-8a197033605a + status: + accessModes: + - ReadWriteOnce + capacity: + storage: 15Gi + phase: Bound +- apiVersion: v1 + kind: Service + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"open-webui-service","namespace":"open-webui"},"spec":{"ports":[{"port":80,"targetPort":8080}],"selector":{"app":"open-webui"}}} + creationTimestamp: "2026-09-17T21:09:55Z" + name: open-webui-service + namespace: open-webui + resourceVersion: "2817759" + uid: 57ffee48-e58c-4639-9c4f-0ee49236e999 + spec: + clusterIP: 10.43.66.33 + clusterIPs: + - 10.43.66.33 + internalTrafficPolicy: Cluster + ipFamilies: + - IPv4 + ipFamilyPolicy: SingleStack + ports: + - port: 80 + protocol: TCP + targetPort: 8080 + selector: + app: open-webui + sessionAffinity: None + type: ClusterIP + status: + loadBalancer: {} +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T21:33:05Z" + labels: + kubernetes.io/metadata.name: adguard + name: adguard + name: adguard + resourceVersion: "41653" + uid: 6d812d6a-1921-4169-9805-7d1192073f46 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-12T20:23:24Z" + labels: + kubernetes.io/metadata.name: bookstack + name: bookstack + name: bookstack + resourceVersion: "829004" + uid: e5c59011-b6b6-4f53-a655-a140b7363443 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"bookstack-db"}} + creationTimestamp: "2026-09-12T20:39:27Z" + labels: + kubernetes.io/metadata.name: bookstack-db + name: bookstack-db + resourceVersion: "834254" + uid: 9600ab15-9b1a-451b-af4e-13ff3b672331 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T22:46:31Z" + labels: + kubernetes.io/metadata.name: cert-manager + name: cert-manager + name: cert-manager + resourceVersion: "61514" + uid: 05b40520-6a11-49af-96df-3d3289a9ed8f + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"cloudflare"}} + creationTimestamp: "2026-09-12T15:12:46Z" + labels: + kubernetes.io/metadata.name: cloudflare + name: cloudflare + resourceVersion: "737700" + uid: f294d89c-0348-444a-b2a8-57303311e45e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: default + name: default + resourceVersion: "266" + uid: ae7a3d3e-3d08-43fe-8e2c-8f72ef066b9e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"gitea"}} + creationTimestamp: "2026-09-21T21:51:14Z" + labels: + kubernetes.io/metadata.name: gitea + name: gitea + resourceVersion: "4409092" + uid: 6398ba34-919b-4392-afbf-ef0054176a1b + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"homepage"}} + creationTimestamp: "2026-09-18T22:22:12Z" + labels: + kubernetes.io/metadata.name: homepage + name: homepage + resourceVersion: "3232692" + uid: eaafb62a-9a9a-45f3-a7b4-7fa4d1e2ae6b + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:46:49Z" + labels: + kubernetes.io/metadata.name: ingress-nginx + name: ingress-nginx + name: ingress-nginx + resourceVersion: "12923" + uid: 46118181-4db6-4c3c-9fdc-f8cc20f28323 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"intel-device-plugin"}} + creationTimestamp: "2026-09-17T20:41:41Z" + labels: + kubernetes.io/metadata.name: intel-device-plugin + name: intel-device-plugin + resourceVersion: "2809324" + uid: b132d304-80cb-4bdc-88cd-2073a8239312 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-node-lease + name: kube-node-lease + resourceVersion: "267" + uid: 81e945a2-a9f5-4ddc-83a8-2bc214a1559e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-public + name: kube-public + resourceVersion: "268" + uid: 2142a69f-f0bd-45af-8738-563df7f99012 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-07-30T20:57:34Z" + labels: + kubernetes.io/metadata.name: kube-system + name: kube-system + resourceVersion: "269" + uid: 1b18f0bc-7131-46b4-9e4b-bb0af3d7d63e + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:35:47Z" + labels: + kubernetes.io/metadata.name: longhorn-system + name: longhorn-system + name: longhorn-system + resourceVersion: "9213" + uid: abd04167-9f4b-468e-a637-1649efff2bc7 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"mumble"}} + creationTimestamp: "2026-09-19T18:14:35Z" + labels: + kubernetes.io/metadata.name: mumble + name: mumble + resourceVersion: "3560003" + uid: cc88370b-9275-43f6-a1f9-2bb663600c48 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"nordvpn"}} + creationTimestamp: "2026-09-11T20:23:38Z" + labels: + kubernetes.io/metadata.name: nordvpn + name: nordvpn + resourceVersion: "417127" + uid: 15f7c8a5-1b18-475f-84dc-90e5963814fa + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"v1","kind":"Namespace","metadata":{"annotations":{},"name":"open-webui"}} + creationTimestamp: "2026-09-17T21:09:55Z" + labels: + kubernetes.io/metadata.name: open-webui + name: open-webui + resourceVersion: "2817739" + uid: 74bf3bef-51bc-405e-8f79-ebc9f52281c6 + spec: + finalizers: + - kubernetes + status: + phase: Active +- apiVersion: v1 + kind: Namespace + metadata: + creationTimestamp: "2026-09-10T19:56:57Z" + labels: + kubernetes.io/metadata.name: portainer + name: portainer + name: portainer + resourceVersion: "16301" + uid: 17802103-8a9c-4b8f-aa42-03a0db58d2ae + spec: + finalizers: + - kubernetes + status: + phase: Active +kind: List +metadata: + resourceVersion: "" diff --git a/portainer/values.yaml b/portainer/values.yaml new file mode 100644 index 0000000..0664c2b --- /dev/null +++ b/portainer/values.yaml @@ -0,0 +1,24 @@ +# values.yaml +ingress: + enabled: true + ingressClassName: nginx + annotations: + # AUTOMATED TLS FIX: Triggers cert-manager to provision the certificate + cert-manager.io/cluster-issuer: "letsencrypt-production" + # THE 502 FIX: Bridges to Portainer's unencrypted HTTP web port cleanly + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + hosts: + - host: portainer.needlestack.boo + paths: + - path: / + pathType: Prefix + # AUTOMATED TLS FIX: Instructs NGINX where to store the secret + tls: + - secretName: portainer-tls-secret + hosts: + - portainer.needlestack.boo +service: + type: ClusterIP +tls: + # Leave this false inside the values block so the pod doesn't look for custom internal certs + force: false diff --git a/qr-barcode-generator/resources.yaml b/qr-barcode-generator/resources.yaml new file mode 100644 index 0000000..031b5e1 --- /dev/null +++ b/qr-barcode-generator/resources.yaml @@ -0,0 +1,74 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: qr-generator +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: qr-generator-ui + namespace: qr-generator + labels: + app: qr-generator +spec: + replicas: 1 + selector: + matchLabels: + app: qr-generator + template: + metadata: + labels: + app: qr-generator + spec: + containers: + - name: generator + image: kavehbc/qrcode-barcode:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8501 + resources: + limits: + memory: "256Mi" + cpu: "500m" + requests: + memory: "128Mi" + cpu: "100m" +--- +apiVersion: v1 +kind: Service +metadata: + name: qr-generator-svc + namespace: qr-generator +spec: + ports: + - port: 80 + targetPort: 8501 + protocol: TCP + selector: + app: qr-generator +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: qr-generator-ingress + namespace: qr-generator + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + tls: + - secretName: qr-generator-tls-secret + hosts: + - qr.needlestack.boo + rules: + - host: qr.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: qr-generator-svc + port: + number: 80 diff --git a/searxng/searxng.yaml b/searxng/searxng.yaml new file mode 100644 index 0000000..0ff4cec --- /dev/null +++ b/searxng/searxng.yaml @@ -0,0 +1,84 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: searxng + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: searxng + namespace: searxng +spec: + replicas: 1 + selector: + matchLabels: + app: searxng + template: + metadata: + labels: + app: searxng + spec: + containers: + - name: searxng + image: searxng/searxng:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + name: http + env: + # THE ANONYMITY BASE: Points SearXNG to your local LAN domain routing + - name: SEARXNG_BASE_URL + value: "https://needlestack.boo" + # SECURITY CRITICAL: Paste your unique openssl output string right here + - name: SEARXNG_SECRET_KEY + value: "0f73d2a5cb68e8ad5827d7584508c78276ebfcf391a9fb74deada8cc04fcaa71" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "1" + +--- +apiVersion: v1 +kind: Service +metadata: + name: searxng-service + namespace: searxng +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 8080 + selector: + app: searxng + type: ClusterIP + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: searxng-local-ingress + namespace: searxng + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-production" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + tls: + - secretName: searxng-tls-secret + hosts: + - search.needlestack.boo + rules: + - host: search.needlestack.boo + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: searxng-service + port: + number: 80