From f3fde1500242436f5c46f428b09c48a8de17f9b6 Mon Sep 17 00:00:00 2001 From: "Andrew M. Stoltz" <1578013+astoltz@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:55:52 -0500 Subject: [PATCH] Update telephony-web image to v20260324d, resolve merge conflicts --- README.md | 4 +- apps/asterisk/deployment.yaml | 300 +++--- apps/asterisk/service.yaml | 80 +- apps/fc-landing/fc-landing.yaml | 640 ++++++------- apps/gitea-public/gitea-public.yaml | 40 +- apps/guacamole/guacamole.yaml | 770 +++++++-------- apps/intranet/intranet.yaml | 271 +++--- apps/irc/irc.yaml | 1362 +++++++++++++-------------- apps/mail/mail.yaml | 518 +++++----- apps/matrix/matrix.yaml | 990 +++++++++---------- apps/noc-services/noc-services.yaml | 514 +++++----- apps/teamspeak/teamspeak.yaml | 290 +++--- apps/voice/voice.yaml | 254 ++--- apps/zabbix/zabbix.yaml | 720 +++++++------- 14 files changed, 3333 insertions(+), 3420 deletions(-) diff --git a/README.md b/README.md index 3239be3..4d74f5c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# bluejay-infra - +# bluejay-infra + Infrastructure manifests for ArgoCD \ No newline at end of file diff --git a/apps/asterisk/deployment.yaml b/apps/asterisk/deployment.yaml index 3667c2a..c8b9846 100644 --- a/apps/asterisk/deployment.yaml +++ b/apps/asterisk/deployment.yaml @@ -1,150 +1,150 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: asterisk - namespace: telephony - labels: - app: asterisk -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: asterisk - template: - metadata: - labels: - app: asterisk - spec: - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - securityContext: - fsGroup: 0 - initContainers: - - name: install-sounds - image: busybox:latest - command: - - sh - - -c - - | - mkdir -p /sounds/en && - wget -qO- http://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-en-ulaw-current.tar.gz | tar xz -C /sounds/en/ && - wget -qO- http://downloads.asterisk.org/pub/telephony/sounds/asterisk-extra-sounds-en-ulaw-current.tar.gz | tar xz -C /sounds/en/ && - echo "Sound files installed: $(find /sounds/en -type f | wc -l) files" - volumeMounts: - - name: sounds - mountPath: /sounds/en - containers: - - name: asterisk - image: localhost/andrius/asterisk:latest - imagePullPolicy: Never - ports: - - name: sip-udp - containerPort: 5060 - protocol: UDP - - name: sip-tcp - containerPort: 5060 - protocol: TCP - - name: ari - containerPort: 8088 - protocol: TCP - volumeMounts: - - name: config-modules - mountPath: /etc/asterisk/modules.conf - subPath: modules.conf - - name: config-http - mountPath: /etc/asterisk/http.conf - subPath: http.conf - - name: config-ari - mountPath: /etc/asterisk/ari.conf - subPath: ari.conf - - name: config-manager - mountPath: /etc/asterisk/manager.conf - subPath: manager.conf - - name: config-pjsip - mountPath: /etc/asterisk/pjsip.conf - subPath: pjsip.conf - - name: config-extensions - mountPath: /etc/asterisk/extensions.conf - subPath: extensions.conf - - name: config-rtp - mountPath: /etc/asterisk/rtp.conf - subPath: rtp.conf - - name: asterisk-data - mountPath: /var/spool/asterisk - - name: asterisk-logs - mountPath: /var/log/asterisk - - name: sounds - mountPath: /var/lib/asterisk/sounds/en - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: "1" - memory: 512Mi - livenessProbe: - tcpSocket: - port: 8088 - initialDelaySeconds: 15 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /ari/asterisk/info - port: 8088 - httpHeaders: - - name: Authorization - value: "Basic Zmxvd2VyY29yZTpibHVlamF5LWFzdGVyaXNrLWFyaQ==" - initialDelaySeconds: 10 - periodSeconds: 5 - volumes: - - name: config-modules - configMap: - name: asterisk-config - items: - - key: modules.conf - path: modules.conf - - name: config-http - configMap: - name: asterisk-config - items: - - key: http.conf - path: http.conf - - name: config-ari - configMap: - name: asterisk-config - items: - - key: ari.conf - path: ari.conf - - name: config-manager - configMap: - name: asterisk-config - items: - - key: manager.conf - path: manager.conf - - name: config-pjsip - configMap: - name: asterisk-config - items: - - key: pjsip.conf - path: pjsip.conf - - name: config-extensions - configMap: - name: asterisk-config - items: - - key: extensions.conf - path: extensions.conf - - name: config-rtp - configMap: - name: asterisk-config - items: - - key: rtp.conf - path: rtp.conf - - name: asterisk-data - persistentVolumeClaim: - claimName: asterisk-data - - name: asterisk-logs - emptyDir: {} - - name: sounds - emptyDir: {} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: asterisk + namespace: telephony + labels: + app: asterisk +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: asterisk + template: + metadata: + labels: + app: asterisk + spec: + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + securityContext: + fsGroup: 0 + initContainers: + - name: install-sounds + image: busybox:latest + command: + - sh + - -c + - | + mkdir -p /sounds/en && + wget -qO- http://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-en-ulaw-current.tar.gz | tar xz -C /sounds/en/ && + wget -qO- http://downloads.asterisk.org/pub/telephony/sounds/asterisk-extra-sounds-en-ulaw-current.tar.gz | tar xz -C /sounds/en/ && + echo "Sound files installed: $(find /sounds/en -type f | wc -l) files" + volumeMounts: + - name: sounds + mountPath: /sounds/en + containers: + - name: asterisk + image: localhost/andrius/asterisk:latest + imagePullPolicy: Never + ports: + - name: sip-udp + containerPort: 5060 + protocol: UDP + - name: sip-tcp + containerPort: 5060 + protocol: TCP + - name: ari + containerPort: 8088 + protocol: TCP + volumeMounts: + - name: config-modules + mountPath: /etc/asterisk/modules.conf + subPath: modules.conf + - name: config-http + mountPath: /etc/asterisk/http.conf + subPath: http.conf + - name: config-ari + mountPath: /etc/asterisk/ari.conf + subPath: ari.conf + - name: config-manager + mountPath: /etc/asterisk/manager.conf + subPath: manager.conf + - name: config-pjsip + mountPath: /etc/asterisk/pjsip.conf + subPath: pjsip.conf + - name: config-extensions + mountPath: /etc/asterisk/extensions.conf + subPath: extensions.conf + - name: config-rtp + mountPath: /etc/asterisk/rtp.conf + subPath: rtp.conf + - name: asterisk-data + mountPath: /var/spool/asterisk + - name: asterisk-logs + mountPath: /var/log/asterisk + - name: sounds + mountPath: /var/lib/asterisk/sounds/en + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: "1" + memory: 512Mi + livenessProbe: + tcpSocket: + port: 8088 + initialDelaySeconds: 15 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /ari/asterisk/info + port: 8088 + httpHeaders: + - name: Authorization + value: "Basic Zmxvd2VyY29yZTpibHVlamF5LWFzdGVyaXNrLWFyaQ==" + initialDelaySeconds: 10 + periodSeconds: 5 + volumes: + - name: config-modules + configMap: + name: asterisk-config + items: + - key: modules.conf + path: modules.conf + - name: config-http + configMap: + name: asterisk-config + items: + - key: http.conf + path: http.conf + - name: config-ari + configMap: + name: asterisk-config + items: + - key: ari.conf + path: ari.conf + - name: config-manager + configMap: + name: asterisk-config + items: + - key: manager.conf + path: manager.conf + - name: config-pjsip + configMap: + name: asterisk-config + items: + - key: pjsip.conf + path: pjsip.conf + - name: config-extensions + configMap: + name: asterisk-config + items: + - key: extensions.conf + path: extensions.conf + - name: config-rtp + configMap: + name: asterisk-config + items: + - key: rtp.conf + path: rtp.conf + - name: asterisk-data + persistentVolumeClaim: + claimName: asterisk-data + - name: asterisk-logs + emptyDir: {} + - name: sounds + emptyDir: {} diff --git a/apps/asterisk/service.yaml b/apps/asterisk/service.yaml index 45675a1..7de3538 100644 --- a/apps/asterisk/service.yaml +++ b/apps/asterisk/service.yaml @@ -1,40 +1,40 @@ -apiVersion: v1 -kind: Service -metadata: - name: asterisk-sip - namespace: telephony - labels: - app: asterisk - annotations: - metallb.universe.tf/loadBalancerIPs: "10.0.56.207" -spec: - type: LoadBalancer - externalTrafficPolicy: Local - selector: - app: asterisk - ports: - - name: sip-udp - port: 5060 - targetPort: 5060 - protocol: UDP - - name: sip-tcp - port: 5060 - targetPort: 5060 - protocol: TCP ---- -apiVersion: v1 -kind: Service -metadata: - name: asterisk-ari - namespace: telephony - labels: - app: asterisk -spec: - type: ClusterIP - selector: - app: asterisk - ports: - - name: ari - port: 8088 - targetPort: 8088 - protocol: TCP +apiVersion: v1 +kind: Service +metadata: + name: asterisk-sip + namespace: telephony + labels: + app: asterisk + annotations: + metallb.universe.tf/loadBalancerIPs: "10.0.56.207" +spec: + type: LoadBalancer + externalTrafficPolicy: Local + selector: + app: asterisk + ports: + - name: sip-udp + port: 5060 + targetPort: 5060 + protocol: UDP + - name: sip-tcp + port: 5060 + targetPort: 5060 + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: asterisk-ari + namespace: telephony + labels: + app: asterisk +spec: + type: ClusterIP + selector: + app: asterisk + ports: + - name: ari + port: 8088 + targetPort: 8088 + protocol: TCP diff --git a/apps/fc-landing/fc-landing.yaml b/apps/fc-landing/fc-landing.yaml index 2e4786c..ad6b4bc 100644 --- a/apps/fc-landing/fc-landing.yaml +++ b/apps/fc-landing/fc-landing.yaml @@ -1,320 +1,320 @@ -# FlowerCore Landing Page -# Blue Jay Lab branded landing page - PUBLIC facing -# ArgoCD managed - BlueJay Lab ---- -apiVersion: v1 -kind: Namespace -metadata: - name: fc-system - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# Landing page HTML (public-safe - no internal LAN references) -apiVersion: v1 -kind: ConfigMap -metadata: - name: fc-landing-html - namespace: fc-system -data: - index.html: | - - - - - - FlowerCore - - - -
- -

FlowerCore

-

Blue Jay Lab

-

- Multi-tenant service management platform built on .NET 10, - Kubernetes, and GitOps. Digital signage, telephony IVR, - MySQL/PHP hosting, and infrastructure automation. -

-
-
- -

Source

-

Gitea repositories

-
- -

Mail

-

Webmail access

-
- -

Chat

-

Matrix messaging

-
- -

GitHub

-

Open source

-
-
-
-
-
17
-
Services
-
-
-
13
-
VLANs
-
-
-
12k+
-
Tests
-
-
- - - ---- -# nginx configuration -apiVersion: v1 -kind: ConfigMap -metadata: - name: fc-landing-nginx-conf - namespace: fc-system -data: - default.conf: | - server { - listen 80; - server_name _; - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ =404; - } - - location /healthz { - access_log off; - return 200 "ok"; - add_header Content-Type text/plain; - } - } ---- -# Landing Page Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: fc-landing - namespace: fc-system - labels: - app: fc-landing -spec: - replicas: 1 - selector: - matchLabels: - app: fc-landing - template: - metadata: - labels: - app: fc-landing - spec: - containers: - - name: nginx - image: nginx:alpine - ports: - - containerPort: 80 - name: http - volumeMounts: - - name: nginx-conf - mountPath: /etc/nginx/conf.d/default.conf - subPath: default.conf - - name: html - mountPath: /usr/share/nginx/html - resources: - requests: - memory: 16Mi - cpu: 5m - limits: - memory: 64Mi - cpu: 50m - livenessProbe: - httpGet: - path: /healthz - port: 80 - initialDelaySeconds: 5 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /healthz - port: 80 - initialDelaySeconds: 3 - periodSeconds: 5 - volumes: - - name: nginx-conf - configMap: - name: fc-landing-nginx-conf - - name: html - configMap: - name: fc-landing-html ---- -apiVersion: v1 -kind: Service -metadata: - name: fc-landing - namespace: fc-system -spec: - selector: - app: fc-landing - ports: - - port: 80 - targetPort: 80 - name: http ---- -# Internal IngressRoute (LAN access) -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: fc-landing - namespace: fc-system -spec: - entryPoints: - - websecure - routes: - - match: Host(`flowercore.iamworkin.lan`) - kind: Rule - services: - - name: fc-landing - port: 80 - tls: {} ---- -# Public IngressRoute (flowercore.io with Cloudflare origin cert) -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: fc-landing-public - namespace: fc-system -spec: - entryPoints: - - websecure - routes: - - match: Host(`flowercore.io`) || Host(`www.flowercore.io`) - kind: Rule - services: - - name: fc-landing - port: 80 - tls: - secretName: cf-origin-flowercore-io ---- -# HTTP to HTTPS redirect for public domain -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: fc-landing-public-http - namespace: fc-system -spec: - entryPoints: - - web - routes: - - match: Host(`flowercore.io`) || Host(`www.flowercore.io`) - kind: Rule - services: - - name: fc-landing - port: 80 - middlewares: - - name: redirect-https ---- -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: redirect-https - namespace: fc-system -spec: - redirectScheme: - scheme: https - permanent: true +# FlowerCore Landing Page +# Blue Jay Lab branded landing page - PUBLIC facing +# ArgoCD managed - BlueJay Lab +--- +apiVersion: v1 +kind: Namespace +metadata: + name: fc-system + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# Landing page HTML (public-safe - no internal LAN references) +apiVersion: v1 +kind: ConfigMap +metadata: + name: fc-landing-html + namespace: fc-system +data: + index.html: | + + + + + + FlowerCore + + + +
+ +

FlowerCore

+

Blue Jay Lab

+

+ Multi-tenant service management platform built on .NET 10, + Kubernetes, and GitOps. Digital signage, telephony IVR, + MySQL/PHP hosting, and infrastructure automation. +

+
+
+ +

Source

+

Gitea repositories

+
+ +

Mail

+

Webmail access

+
+ +

Chat

+

Matrix messaging

+
+ +

GitHub

+

Open source

+
+
+
+
+
17
+
Services
+
+
+
13
+
VLANs
+
+
+
12k+
+
Tests
+
+
+ + + +--- +# nginx configuration +apiVersion: v1 +kind: ConfigMap +metadata: + name: fc-landing-nginx-conf + namespace: fc-system +data: + default.conf: | + server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ =404; + } + + location /healthz { + access_log off; + return 200 "ok"; + add_header Content-Type text/plain; + } + } +--- +# Landing Page Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fc-landing + namespace: fc-system + labels: + app: fc-landing +spec: + replicas: 1 + selector: + matchLabels: + app: fc-landing + template: + metadata: + labels: + app: fc-landing + spec: + containers: + - name: nginx + image: nginx:alpine + ports: + - containerPort: 80 + name: http + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + - name: html + mountPath: /usr/share/nginx/html + resources: + requests: + memory: 16Mi + cpu: 5m + limits: + memory: 64Mi + cpu: 50m + livenessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 3 + periodSeconds: 5 + volumes: + - name: nginx-conf + configMap: + name: fc-landing-nginx-conf + - name: html + configMap: + name: fc-landing-html +--- +apiVersion: v1 +kind: Service +metadata: + name: fc-landing + namespace: fc-system +spec: + selector: + app: fc-landing + ports: + - port: 80 + targetPort: 80 + name: http +--- +# Internal IngressRoute (LAN access) +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: fc-landing + namespace: fc-system +spec: + entryPoints: + - websecure + routes: + - match: Host(`flowercore.iamworkin.lan`) + kind: Rule + services: + - name: fc-landing + port: 80 + tls: {} +--- +# Public IngressRoute (flowercore.io with Cloudflare origin cert) +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: fc-landing-public + namespace: fc-system +spec: + entryPoints: + - websecure + routes: + - match: Host(`flowercore.io`) || Host(`www.flowercore.io`) + kind: Rule + services: + - name: fc-landing + port: 80 + tls: + secretName: cf-origin-flowercore-io +--- +# HTTP to HTTPS redirect for public domain +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: fc-landing-public-http + namespace: fc-system +spec: + entryPoints: + - web + routes: + - match: Host(`flowercore.io`) || Host(`www.flowercore.io`) + kind: Rule + services: + - name: fc-landing + port: 80 + middlewares: + - name: redirect-https +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: redirect-https + namespace: fc-system +spec: + redirectScheme: + scheme: https + permanent: true diff --git a/apps/gitea-public/gitea-public.yaml b/apps/gitea-public/gitea-public.yaml index 9d91dfd..2f142fd 100644 --- a/apps/gitea-public/gitea-public.yaml +++ b/apps/gitea-public/gitea-public.yaml @@ -1,20 +1,20 @@ -# Gitea Public IngressRoute -# Routes gitea.flowercore.io to internal Gitea service via Cloudflare origin cert -# ArgoCD managed - BlueJay Lab ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: gitea-public - namespace: gitea -spec: - entryPoints: - - websecure - routes: - - match: Host(`gitea.flowercore.io`) - kind: Rule - services: - - name: gitea-http - port: 3000 - tls: - secretName: cf-origin-flowercore-io +# Gitea Public IngressRoute +# Routes gitea.flowercore.io to internal Gitea service via Cloudflare origin cert +# ArgoCD managed - BlueJay Lab +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: gitea-public + namespace: gitea +spec: + entryPoints: + - websecure + routes: + - match: Host(`gitea.flowercore.io`) + kind: Rule + services: + - name: gitea-http + port: 3000 + tls: + secretName: cf-origin-flowercore-io diff --git a/apps/guacamole/guacamole.yaml b/apps/guacamole/guacamole.yaml index 4c5255d..3963e86 100644 --- a/apps/guacamole/guacamole.yaml +++ b/apps/guacamole/guacamole.yaml @@ -1,426 +1,344 @@ -# Apache Guacamole - Blue Jay Remote Access -# FlowerCore Infrastructure Gateway -# MySQL 8 + guacd + guacamole web (Blue Jay branded) -# ArgoCD managed - BlueJay Lab -# ALL credentials sourced from 1Password via OnePasswordItem CRD (guacamole-credentials) -# Custom image: fc-guacamole:bluejay (Blue Jay branding + 1Password vault extension) ---- -apiVersion: v1 -kind: Namespace -metadata: - name: guacamole - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# MySQL 8 StatefulSet -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: guac-mysql - namespace: guacamole - labels: - app: guac-mysql -spec: - serviceName: guac-mysql - replicas: 1 - selector: - matchLabels: - app: guac-mysql - template: - metadata: - labels: - app: guac-mysql - spec: - containers: - - name: mysql - image: mysql:8.0 - ports: - - containerPort: 3306 - name: mysql - env: - - name: MYSQL_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Root-Password - - name: MYSQL_DATABASE - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Name - - name: MYSQL_USER - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-User - - name: MYSQL_PASSWORD - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Password - volumeMounts: - - name: guac-mysql-data - mountPath: /var/lib/mysql - resources: - requests: - memory: 256Mi - cpu: 100m - limits: - memory: 1Gi - cpu: 500m - livenessProbe: - exec: - command: - - mysqladmin - - ping - - -h - - localhost - initialDelaySeconds: 60 - periodSeconds: 10 - readinessProbe: - exec: - command: - - mysqladmin - - ping - - -h - - localhost - initialDelaySeconds: 30 - periodSeconds: 5 - volumeClaimTemplates: - - metadata: - name: guac-mysql-data - spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 5Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: guac-mysql - namespace: guacamole -spec: - selector: - app: guac-mysql - ports: - - port: 3306 - targetPort: 3306 - name: mysql - clusterIP: None ---- -# DB schema init Job -apiVersion: batch/v1 -kind: Job -metadata: - name: guacamole-initdb - namespace: guacamole - annotations: - argocd.argoproj.io/hook: PostSync - argocd.argoproj.io/hook-delete-policy: BeforeHookCreation -spec: - ttlSecondsAfterFinished: 300 - template: - spec: - restartPolicy: OnFailure - initContainers: - - name: wait-for-mysql - image: mysql:8.0 - command: - - sh - - -c - - | - until mysqladmin ping -h guac-mysql --silent; do - echo "Waiting for MySQL..." - sleep 5 - done - containers: - - name: initdb - image: guacamole/guacamole:latest - command: - - sh - - -c - - | - /opt/guacamole/bin/initdb.sh --mysql > /tmp/initdb.sql - mysql -h guac-mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /tmp/initdb.sql || true - env: - - name: MYSQL_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Root-Password - - name: MYSQL_DATABASE - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Name ---- -# guacd (Guacamole daemon) -apiVersion: apps/v1 -kind: Deployment -metadata: - name: guacd - namespace: guacamole - labels: - app: guacd -spec: - replicas: 1 - selector: - matchLabels: - app: guacd - template: - metadata: - labels: - app: guacd - spec: - containers: - serviceAccountName: guacd-exec - - name: guacd - image: guacamole/guacd:latest - ports: - - containerPort: 4822 - name: guacd - resources: - requests: - memory: 128Mi - cpu: 100m - limits: - memory: 512Mi - cpu: 500m - livenessProbe: - tcpSocket: - port: 4822 - initialDelaySeconds: 15 - periodSeconds: 10 ---- -apiVersion: v1 -kind: Service -metadata: - name: guacd - namespace: guacamole -spec: - selector: - app: guacd - ports: - - port: 4822 - targetPort: 4822 - name: guacd ---- -# Guacamole Properties ConfigMap -apiVersion: v1 -kind: ConfigMap -metadata: - name: guacamole-properties - namespace: guacamole - labels: - app: guacamole -data: - guacamole.properties: | - # Blue Jay Remote Access — Guacamole Configuration - # MySQL/guacd settings provided via env vars — do NOT duplicate here - - # 1Password Vault Integration - 1password-connect-url: http://onepassword-connect.onepassword-system.svc.cluster.local:8080 - 1password-connect-token: placeholder-configure-via-secret - 1password-vault-id: qaphopopkryhbg353ukzhhuqoq - - # Extension Priority - extension-priority: mysql, ban, bluejay, 1password-vault, * - - # Ban (brute force) - ban-max-invalid-attempts: 5 - ban-address-duration: 300000 - ban-max-addresses: 1000 - - # TOTP - totp-issuer: Blue Jay Remote Access - totp-digits: 6 - totp-period: 30 - totp-mode: sha256 - - # Session Recording - recording-search-path: /var/lib/guacamole/recordings - - # Logging - log-level: info - - # API Token Expiry - api-session-timeout: 60 ---- -# Guacamole Web Application — Blue Jay branded -apiVersion: apps/v1 -kind: Deployment -metadata: - name: guacamole - namespace: guacamole - labels: - app: guacamole -spec: - replicas: 1 - selector: - matchLabels: - app: guacamole - template: - metadata: - labels: - app: guacamole - spec: - containers: - - name: guacamole - image: localhost/fc-guacamole:bluejay - imagePullPolicy: Never - ports: - - containerPort: 8080 - name: http - env: - - name: GUACD_HOSTNAME - value: guacd - - name: GUACD_PORT - value: "4822" - - name: MYSQL_HOSTNAME - value: guac-mysql - - name: MYSQL_PORT - value: "3306" - - name: MYSQL_DATABASE - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Name - - name: MYSQL_USER - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-User - - name: MYSQL_PASSWORD - valueFrom: - secretKeyRef: - name: guacamole-credentials - key: DB-Password - volumeMounts: - - name: guac-properties - mountPath: /etc/guacamole/guacamole.properties - subPath: guacamole.properties - resources: - requests: - memory: 256Mi - cpu: 100m - limits: - memory: 1Gi - cpu: 500m - livenessProbe: - httpGet: - path: /guacamole/ - port: 8080 - initialDelaySeconds: 120 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /guacamole/ - port: 8080 - initialDelaySeconds: 60 - periodSeconds: 5 - volumes: - - name: guac-properties - configMap: - name: guacamole-properties ---- -apiVersion: v1 -kind: Service -metadata: - name: guacamole - namespace: guacamole -spec: - selector: - app: guacamole - ports: - - port: 8080 - targetPort: 8080 - name: http ---- -# Traefik addPrefix middleware -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: guac-add-prefix - namespace: guacamole -spec: - addPrefix: - prefix: /guacamole ---- -# TLS Certificate via cert-manager -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: guacamole-tls - namespace: guacamole -spec: - secretName: guacamole-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - guac.iamworkin.lan ---- -# Traefik IngressRoute -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: guacamole - namespace: guacamole -spec: - entryPoints: - - websecure - routes: - - match: Host(`guac.iamworkin.lan`) - kind: Rule - middlewares: - - name: guac-add-prefix - services: - - name: guacamole - port: 8080 - tls: - secretName: guacamole-tls ---- -# 1Password secret sync -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: guacamole-credentials - namespace: guacamole -spec: - itemPath: vaults/IAmWorkin/items/Guacamole ---- -# RBAC for guacd K8s exec protocol -apiVersion: v1 -kind: ServiceAccount -metadata: - name: guacd-exec - namespace: guacamole ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: guacd-pod-exec -rules: - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["pods/exec"] - verbs: ["create"] - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["list"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: guacd-pod-exec -subjects: - - kind: ServiceAccount - name: guacd-exec - namespace: guacamole -roleRef: - kind: ClusterRole - name: guacd-pod-exec - apiGroup: rbac.authorization.k8s.io +# Apache Guacamole - Remote Desktop Gateway +# MySQL 8 + guacd + guacamole web +# ArgoCD managed - BlueJay Lab +# ALL credentials sourced from 1Password via OnePasswordItem CRD (guacamole-credentials) +# Fields: username, password, DB-User, DB-Password, DB-Root-Password, DB-Name, URL +--- +apiVersion: v1 +kind: Namespace +metadata: + name: guacamole + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# MySQL 8 StatefulSet +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: guac-mysql + namespace: guacamole + labels: + app: guac-mysql +spec: + serviceName: guac-mysql + replicas: 1 + selector: + matchLabels: + app: guac-mysql + template: + metadata: + labels: + app: guac-mysql + spec: + containers: + - name: mysql + image: mysql:8.0 + ports: + - containerPort: 3306 + name: mysql + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Root-Password + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Name + - name: MYSQL_USER + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-User + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Password + volumeMounts: + - name: guac-mysql-data + mountPath: /var/lib/mysql + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 500m + livenessProbe: + exec: + command: + - mysqladmin + - ping + - -h + - localhost + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + exec: + command: + - mysqladmin + - ping + - -h + - localhost + initialDelaySeconds: 30 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: guac-mysql-data + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: guac-mysql + namespace: guacamole +spec: + selector: + app: guac-mysql + ports: + - port: 3306 + targetPort: 3306 + name: mysql + clusterIP: None +--- +# DB schema init Job +# Generates the MySQL schema and pipes it into the database +apiVersion: batch/v1 +kind: Job +metadata: + name: guacamole-initdb + namespace: guacamole + annotations: + argocd.argoproj.io/hook: PostSync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation +spec: + ttlSecondsAfterFinished: 300 + template: + spec: + restartPolicy: OnFailure + initContainers: + - name: wait-for-mysql + image: mysql:8.0 + command: + - sh + - -c + - | + until mysqladmin ping -h guac-mysql --silent; do + echo "Waiting for MySQL..." + sleep 5 + done + containers: + - name: initdb + image: guacamole/guacamole:latest + command: + - sh + - -c + - | + # Generate schema SQL + /opt/guacamole/bin/initdb.sh --mysql > /tmp/initdb.sql + # Apply schema (ignore errors if tables already exist) + mysql -h guac-mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /tmp/initdb.sql || true + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Root-Password + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Name +--- +# guacd (Guacamole daemon) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: guacd + namespace: guacamole + labels: + app: guacd +spec: + replicas: 1 + selector: + matchLabels: + app: guacd + template: + metadata: + labels: + app: guacd + spec: + containers: + - name: guacd + image: guacamole/guacd:latest + ports: + - containerPort: 4822 + name: guacd + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m + livenessProbe: + tcpSocket: + port: 4822 + initialDelaySeconds: 15 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: guacd + namespace: guacamole +spec: + selector: + app: guacd + ports: + - port: 4822 + targetPort: 4822 + name: guacd +--- +# Guacamole Web Application +apiVersion: apps/v1 +kind: Deployment +metadata: + name: guacamole + namespace: guacamole + labels: + app: guacamole +spec: + replicas: 1 + selector: + matchLabels: + app: guacamole + template: + metadata: + labels: + app: guacamole + spec: + containers: + - name: guacamole + image: guacamole/guacamole:latest + ports: + - containerPort: 8080 + name: http + env: + - name: GUACD_HOSTNAME + value: guacd + - name: GUACD_PORT + value: "4822" + - name: MYSQL_HOSTNAME + value: guac-mysql + - name: MYSQL_PORT + value: "3306" + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Name + - name: MYSQL_USER + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-User + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: DB-Password + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 500m + livenessProbe: + httpGet: + path: /guacamole/ + port: 8080 + initialDelaySeconds: 120 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /guacamole/ + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: guacamole + namespace: guacamole +spec: + selector: + app: guacamole + ports: + - port: 8080 + targetPort: 8080 + name: http +--- +# Traefik addPrefix middleware +# External URL guac.iamworkin.lan/ gets prefix /guacamole added +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: guac-add-prefix + namespace: guacamole +spec: + addPrefix: + prefix: /guacamole +--- +# TLS Certificate via cert-manager +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: guacamole-tls + namespace: guacamole +spec: + secretName: guacamole-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - guac.iamworkin.lan +--- +# Traefik IngressRoute +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: guacamole + namespace: guacamole +spec: + entryPoints: + - websecure + routes: + - match: Host(`guac.iamworkin.lan`) + kind: Rule + middlewares: + - name: guac-add-prefix + services: + - name: guacamole + port: 8080 + tls: + secretName: guacamole-tls +--- +# 1Password secret sync — creates guacamole-credentials K8s Secret +# Fields: username, password, DB-User, DB-Password, DB-Root-Password, DB-Name, URL +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: guacamole-credentials + namespace: guacamole +spec: + itemPath: vaults/IAmWorkin/items/Guacamole diff --git a/apps/intranet/intranet.yaml b/apps/intranet/intranet.yaml index ecf9367..9761aef 100644 --- a/apps/intranet/intranet.yaml +++ b/apps/intranet/intranet.yaml @@ -1,138 +1,133 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - app.kubernetes.io/part-of: bluejay-infra - name: intranet ---- -apiVersion: v1 -data: - default.conf: "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n\n location / {\n try_files $uri $uri/ =404;\n }\n\n location /healthz {\n access_log off;\n return 200 \"ok\";\n add_header Content-Type text/plain;\n }\n}\n" -kind: ConfigMap -metadata: - name: intranet-nginx-conf - namespace: intranet ---- -apiVersion: v1 -data: - index.html: "\n\n\n\n\nBlue Jay Lab Intranet - FlowerCore\n\n\n\n\n
\n \n

Blue Jay Lab Intranet

\n

BlueJay Network Infrastructure — 13 VLANs | 9 Physical Nodes | RKE2 Bare-Metal Cluster | 5 WiFi SSIDs | 18 Domains | 22 ArgoCD Apps | NAS Storage | 4 Pi Devices

\n

Last updated: 2026-03-21 — PiManager deployed (piez+pirelay), 14 Guacamole connections, 13 Zabbix hosts, SSH keys on all nodes, Prometheus 15 scrape targets, pirelay node-exporter+zabbix-agent2 installed

\n
Network Rebuild 100% Complete — /28 Fully Live — 22 ArgoCD Apps Healthy — 4 Pi Fleet Nodes
\n
\n\n\n\n\n
\n

Overview

\n
\n
13
VLANs
\n
9
Physical Nodes
\n
3
RKE2 Nodes
\n
22
ArgoCD Apps
\n
13
Zabbix Hosts
\n
4
Pi Devices
\n
5
WiFi SSIDs
\n
8
VPN Tunnels
\n
13
Public IPs
\n
18
Domains
\n
14
Guac Connections
\n
9.1 TB
NAS Storage
\n
\n\n
Network Status: ALL PHASES COMPLETE + TELEPHONY LIVE + PI FLEET DEPLOYED. K3s on noc1, bare-metal RKE2 on 3 NUCs. /28 FULLY LIVE: 13 VIPs, 30 port forwards (SIP+RTP added), Cloudflare DNS. ArgoCD: 11 apps all Healthy (via bluejay-infra ApplicationSet, incl infra-asterisk + infra-noc-services). Asterisk PBX: MetalLB .207, 4 PJSIP extensions, Twilio SIP trunk, calls working end-to-end. telephony-web: 32 MCP tools wired+deployed. 1Password: 7 services wired, 45+ vault items. Credential rotation 17/17 complete. BlueJayNAS (DS1621+) online with Longhorn backup. Zabbix: 13 hosts monitored. Pi Fleet: 4 devices (edge1 Pi5+Hailo, edge2 Pi4, piez Pi4+EZConnect, pirelay Pi3+Relay). FlowerCore.PiManager deployed to piez (:5000) and pirelay (:5100) — unified config-driven GPIO/relay/I2C/SPI management. Guacamole: 14 connections in 4 groups (all nodes). SSH ed25519 keys deployed to all 9 physical nodes.
\n\n

Quick Links — Web UIs

\n
\n
pfSense
https://pfsense.iamworkin.lan
\n
UniFi Cloud Key
https://unifi.iamworkin.lan
\n
Synology WiFi (SRM)
http://wifi.iamworkin.lan:8000
\n
Element Web (Matrix)
https://element.iamworkin.lan
\n
Cockpit (noc1)
https://cockpit.iamworkin.lan
\n
Grafana
https://grafana.iamworkin.lan
\n
Prometheus
https://prometheus.iamworkin.lan
\n
Guacamole
https://guac.iamworkin.lan
\n
PKI Web
https://pki.iamworkin.lan
\n
ArgoCD
https://argocd.iamworkin.lan
\n
Zabbix
https://zabbix.iamworkin.lan
\n
Gitea
https://gitea.iamworkin.lan
\n
Frontier Modem
http://192.168.254.254
\n
BlueJayNAS (DSM)
https://nas.iamworkin.lan:5001
\n
Matrix Synapse
https://matrix.iamworkin.lan
\n
Telephony
https://telephony.iamworkin.lan
\n
Intranet
https://intranet.iamworkin.lan
\n
PiManager (piez)
http://piez.iamworkin.lan:5000
\n
PiManager (pirelay)
http://pirelay.iamworkin.lan:5100
\n
Print Service
http://print.iamworkin.lan:5200
\n
Selenium Grid
https://selenium.iamworkin.lan
\n
Frigate NVR
http://edge1.iamworkin.lan:5000
\n
\n\n

Phase Progress

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PhaseDescriptionStatusProgress
1Frontier Modem ConfigDone100%
2pfSense Base (WAN, LAN, VIPs)Done100%
3VLAN Configuration (14 VLANs)Done100%
4Firewall Rules & AliasesDone100%
5Bare-Metal RKE2 (Harvester Decommissioned)Done100%
6OpenVPN (8 servers)Done100%
7NAT ConfigurationDone100%
8Traffic ShaperDone100%
9DNS + NTP + SNMPDone100%
10Switch + WiFi ConfigDone100%
11NOC1 + Bare-Metal RKE2Done100%
12GitOps + IaC (ArgoCD/Puppet)Done100%
13Documentation SyncDone100%
\n
\n\n\n
\n

ISP & WAN

\n
\n
\n
ISP: Frontier Communications
\n
    \n
  • Service: 1000/1000 Mbps fiber
  • \n
  • Account: 952-431-5646-020421-7
  • \n
  • Measured: 925 down / 677 up (MGMT VLAN)
  • \n
\n
\n
\n
Modem: NVG468MQ
\n \n
\n
\n

WAN Status

\n\n\n\n\n\n\n\n\n\n
PropertyValue
pfSense WAN Interfaceix3 (DHCP from modem)
pfSense WAN IP192.168.254.122 (double NAT intentional)
Public /28 Block74.40.140.16/28
Gateway74.40.140.30
Usable Range74.40.140.17 – 74.40.140.29 (13 IPs)
\n
ISP /28 Routing: FULLY OPERATIONAL. Public subnet (74.40.140.16/28) is live. 13 VIPs on WAN (ix3), 12 hybrid outbound NAT rules, 28 port forwards. Cloudflare DNS with 28+ A records pointing to /28 IPs. DDNS: gateway.iamwork.in updates pfSense WAN DHCP IP via Cloudflare API.
\n

Modem Static Routes

\n\n\n\n\n\n\n
NameDestinationGatewayInterface
pfSense-Public-2874.40.140.16/28192.168.254.122LAN
pfSense-Private-Subnets10.0.0.0/8192.168.254.122LAN
\n

Public IP Allocation (13 usable)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
IPFull AddressAssignmentVLAN(s)Services
.1674.40.140.16Network addressUnusable
.1774.40.140.17 ANDREW + VPN60Andrew tenant primary + VPN :1194/:1195
.1874.40.140.18 ANDREW #260Andrew secondary
.1974.40.140.19 MATT + VPN61Matt tenant primary + VPN :1194/:1195
.2074.40.140.20 MATT #261Matt secondary
.2174.40.140.21 DUSTIN + VPN62Dustin tenant primary + VPN :1194/:1195
.2274.40.140.22 SIERRA (Dustin #2)62Dustin secondary
.2374.40.140.23 ERIK + VPN63Erik tenant primary + VPN :1194/:1195
.2474.40.140.24 PROD57K8s web + mail (flowercore.io, SMTP)
.2574.40.140.25 FIT + VPN69FIT tenant primary + VPN :1194/:1195
.2674.40.140.26 FIT #269FIT secondary
.2774.40.140.27 COMMS57TeamSpeak, IRC, Matrix
.2874.40.140.28 SHARED59,64,65,66,67WORK+SCHOOL+GUEST+VOIP+EMPLOYEE outbound
.2974.40.140.29 HOME58Home traffic + Nintendo Switch static port NAT
.3074.40.140.30Gateway (Frontier)ISP router
.3174.40.140.31BroadcastUnusable
\n
\n\n\n
\n

pfSense Firewall

\n
\n
\n
Netgate 4100
\n \n
\n
\n
Firewall Stats
\n
    \n
  • Aliases: 36 (16 port, 5 host, 15 network)
  • \n
  • Rules: 90 active
  • \n
  • Policy: Air-gapped default — deny all, explicit allow
  • \n
  • SNMP: community \U0001F510 SNMP Community
  • \n
  • SNMP Modules: mibII, netgraph, pf, hostres, bridge
  • \n
\n
\n
\n
Services
\n
    \n
  • DNS: Unbound (DNSSEC, WAN-only outgoing, prefetch)
  • \n
  • DHCP: dhcpd on all 12 VLAN interfaces (.100-.199)
  • \n
  • NTP: ntpd on all VLAN interfaces, DHCP option 42
  • \n
  • Traffic Shaper: 24 dummynet pipes, fq_codel
  • \n
\n
\n
\n

VLAN Configuration (13 VLANs)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VLANNameSubnetDHCP RangeDown/Up (Mbps)PriorityPublic IP
56MGMT10.0.56.0/24.100-.199500 / 5005WAN DHCP
57PROD10.0.57.0/24.100-.199500 / 5005.24
58HOME10.0.58.0/24.100-.199800 / 8003.29
59EMPLOYEE10.0.59.0/24.100-.199500 / 5003.28 (shared)
60ANDREW10.0.60.0/24.100-.199300 / 3003.17
61MATT10.0.61.0/24.100-.199300 / 3003.19
62DUSTIN10.0.62.0/24.100-.199300 / 3003.21
63ERIK10.0.63.0/24.100-.199300 / 3003.23
64WORK10.0.64.0/24.100-.199500 / 5003.28 (shared)
65SCHOOL10.0.65.0/24.100-.199200 / 2001.28 (shared)
66GUEST10.0.66.0/24.100-.199100 / 501.28 (shared)
67VOIP10.0.67.0/24.100-.199100 / 1007.28 (shared)
69FIT10.0.69.0/24.100-.199300 / 3003.25
\n
Firewall Policy: MGMT has full access. HOME/WORK/SCHOOL get general internet. GUEST isolated except PROD web. Tenants fully isolated from each other — only PROD, DNS, NAS, and internet. VOIP is SIP-only outbound.
\n
\n\n\n
\n

Switching & WiFi

\n
\n
\n
UniFi Switch USW-Lite-16-PoE
\n \n
\n
\n
UniFi Cloud Key G2
\n \n
\n
\n
Synology RT6600AX (AP Mode)
\n \n
\n
\n
BlueJayNAS (Synology DS1621+)
\n
    \n
  • IP: nas.iamworkin.lan (HOME VLAN 58)
  • \n
  • DSM: https://nas.iamworkin.lan:5001
  • \n
  • Credentials: \U0001F510 BlueJayNAS
  • \n
  • Storage: 9.1 TB Btrfs (RAID)
  • \n
  • NFS Exports: Longhorn backup, shared media, ISO library
  • \n
  • SNMP: Enabled (Zabbix monitored)
  • \n
  • Zabbix: Host monitored via SNMP v2c
  • \n
  • Switch Port: 14 (Access, VLAN 58)
  • \n
\n
\n
\n

Switch Port Assignments

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PortDeviceModeVLANStatus
1pfSense UplinkTrunk (All)56-67UP
2rke2-agent2Trunk (All)56-67UP
3WiFi Uplink (Synology)Trunk, native 5857-67UP
4rke2-agent1Trunk (All)56-67UP
5Cloud Key G2Access56 (MGMT)UP (PoE)
6rke2-serverTrunk (All)56-67UP
7AvailableDown
8noc1Trunk (All)56-67UP
9WorkstationAccess56 (MGMT)UP
10AvailableDown
11edge2 (Pi 4)Access57 (PROD)UP
12AvailableDown
13edge1 (Pi 5)Access57 (PROD)UP
14Synology NASAccess58 (HOME)UP
15AvailableDown
16Synology 2Access58 (HOME)UP
\n

WiFi SSIDs

\n\n\n\n\n\n\n\n\n\n
SSIDBridgeVLANTypePassword
BlueJay-Homebr0untagged (58)Primary\U0001F510 BlueJay-Home WiFi
BlueJay-Employeebr259Custom\U0001F510 BlueJay-Employee WiFi
BlueJay-Workbr364Custom\U0001F510 BlueJay-Work WiFi
BlueJay-Schoolbr465Custom\U0001F510 BlueJay-School WiFi
BlueJay-Guestgbr066Guest (isolation+NAT)\U0001F510 BlueJay-Guest WiFi
\n
\n\n\n
\n

DNS Directory

\n
All entries are pfSense Unbound host overrides under iamworkin.lan. 52+ host overrides configured, plus 4 tenant wildcard redirect zones (*.bluejay.lan, *.timefortaco.lan, *.erik.lan, *.flowerinsider.lan → 10.0.56.200 Traefik).
\n

Management Devices

\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
pfsense.iamworkin.lanpfsense.iamworkin.lan pfSense firewall
switch.iamworkin.lanswitch.iamworkin.lan UniFi PoE Switch
unifi.iamworkin.lanunifi.iamworkin.lan UniFi Cloud Key G2
wifi.iamworkin.lanwifi.iamworkin.lan Synology WiFi Router (AP)
nas.iamworkin.lannas.iamworkin.lan Synology NAS
\n

RKE2 Bare-Metal Cluster

\n\n\n\n\n\n\n\n
HostnameIPRole
rke2-server.iamworkin.lanrke2-server.iamworkin.lan RKE2 control plane (bare-metal, openSUSE Leap 16)
rke2-agent1.iamworkin.lanrke2-agent1.iamworkin.lan RKE2 worker node 1 (bare-metal, openSUSE Leap 16)
rke2-agent2.iamworkin.lanrke2-agent2.iamworkin.lan RKE2 worker node 2 (bare-metal, openSUSE Leap 16)
\n

NOC Services (noc1)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
noc1.iamworkin.lannoc1.iamworkin.lan NOC management node (K3s)
acme.iamworkin.lannoc1.iamworkin.lanstep-ca ACME CA
pki.iamworkin.lannoc1.iamworkin.lanPKI cert/CRL distribution
guac.iamworkin.lannoc1.iamworkin.lanApache Guacamole
grafana.iamworkin.lantraefik.iamworkin.lanGrafana monitoring (monitoring ns, noc1 Podman stopped 2026-03-18, K8s only)
prometheus.iamworkin.lantraefik.iamworkin.lanPrometheus metrics (monitoring ns, noc1 Podman stopped 2026-03-18, K8s only)
cockpit.iamworkin.lantraefik.iamworkin.lanCockpit web console (noc-proxy ns → noc1:9090)
traefik.iamworkin.lannoc1.iamworkin.lanTraefik dashboard (K3s, legacy)
op-connect.iamworkin.lannoc1.iamworkin.lan1Password Connect API (:8180)
\n

RKE2 Services (via Traefik at traefik.iamworkin.lan)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
rke2.iamworkin.lanrke2-server.iamworkin.lan RKE2 API server (bare-metal control plane)
rke2-traefik.iamworkin.lantraefik.iamworkin.lan Traefik LoadBalancer (MetalLB)
argocd.iamworkin.lantraefik.iamworkin.lanArgoCD GitOps (22 apps, all Healthy)
gitea.iamworkin.lantraefik.iamworkin.lanGitea Git hosting (SSH at MetalLB .201)
zabbix.iamworkin.lantraefik.iamworkin.lanZabbix monitoring (10 hosts, trapper at .203)
guac.iamworkin.lantraefik.iamworkin.lanApache Guacamole (17 connections)
irc.iamworkin.lantraefik.iamworkin.lanUnrealIRCd + Anope (ports 6667/6697/8067)
matrix.iamworkin.lantraefik.iamworkin.lanMatrix Synapse homeserver
element.iamworkin.lantraefik.iamworkin.lanElement Web (Matrix client)
intranet.iamworkin.lantraefik.iamworkin.lanLab intranet dashboard
pki.iamworkin.lantraefik.iamworkin.lanPKI cert/CRL distribution
mail.iamworkin.lantraefik.iamworkin.landocker-mailserver (SMTP at MetalLB .202)
telephony.iamwork.intraefik.iamworkin.lanFlowerCore.Telephony (:5100, Cloudflare origin cert)
telephony.iamworkin.lantraefik.iamworkin.lanFlowerCore.Telephony (internal, step-ca cert)
grafana.iamworkin.lantraefik.iamworkin.lanGrafana (monitoring ns, noc1 Podman stopped 2026-03-18, K8s only)
prometheus.iamworkin.lantraefik.iamworkin.lanPrometheus (monitoring ns, noc1 Podman stopped 2026-03-18, K8s only)
cockpit.iamworkin.lantraefik.iamworkin.lanCockpit (noc-proxy ns → noc1:9090)
\n

RKE2 MetalLB Service IPs

\n\n\n\n\n\n\n\n\n\n
IPServicePorts
traefik.iamworkin.lanTraefik Ingress80, 443, 8080, 6667, 6697
gitea-ssh.iamworkin.lanGitea SSH22
mail.iamworkin.lanMail SMTP25, 465, 587
zabbix-trapper.iamworkin.lanZabbix Trapper10051
ts.iamworkin.lanTeamSpeak9987/UDP, 30033, 10011
\n

Production / Edge Nodes

\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
macmini.iamworkin.lanmacmini.iamworkin.lan Mac Mini build node (Xcode)
edge1.iamworkin.lanedge1.iamworkin.lan Pi 5 + Hailo AI HAT+ 2
edge2.iamworkin.lanedge2.iamworkin.lan Pi 4 (Argon ONE, CI runner)
piez.iamworkin.lanpiez.iamworkin.lan Pi 4 + EZ Connect (PiManager :5000, GPIO/I2C/SPI)
pirelay.iamworkin.lanpirelay.iamworkin.lan Pi 3 + 4-ch Relay (PiManager :5100, KS0212)
\n

Planned / Windows (pre-registered)

\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
dc1.iamworkin.lan10.0.56.20 AD Domain Controller (planned)
wac1.iamworkin.lan10.0.56.21 Windows Admin Center (planned)
rds1.iamworkin.lan10.0.57.20 Remote Desktop Services (planned)
iis1.iamworkin.lan10.0.57.21 IIS Web Server (planned)
proxy.iamworkin.lan10.0.56.22 Squid Authenticated Proxy (planned)
\n
\n\n\n
\n

Kubernetes Clusters

\n

K3s (noc1 — Emergency Fallback, Scaled to 0)

\n
\n
\n
K3s on noc1 (Standby)
\n
    \n
  • Node: noc1.iamworkin.lan (single-node)
  • \n
  • Version: K3s v1.34.5
  • \n
  • Status: Scaled to 0 — emergency fallback only
  • \n
  • Migration: All workloads moved to RKE2 (2026-03-09)
  • \n
  • Tools: kubectl v1.35.2, helm v3.20.0
  • \n
\n
\n
\n

Harvester HCI (DECOMMISSIONED 2026-03-09)

\n
\n
\n
Harvester Cluster — Decommissioned
\n
    \n
  • Status: Decommissioned — replaced by bare-metal RKE2
  • \n
  • Reason: 6 K8s control planes caused 100°C thermal throttling
  • \n
  • Migration: All 3 NUCs reformatted to openSUSE Leap 16, bare-metal RKE2
  • \n
  • Result: Temps 44-71°C, 1 control plane instead of 6
  • \n
\n
\n
\n

RKE2 (Bare-Metal Cluster)

\n
\n
\n
RKE2 Cluster
\n
    \n
  • Version: RKE2 v1.34.5+rke2r1
  • \n
  • OS: openSUSE Leap 16.0 (bare-metal)
  • \n
  • CNI: Calico (VXLAN mode)
  • \n
  • Pod CIDR: 10.42.0.0/16
  • \n
  • Service CIDR:\ - \ 10.43.0.0/16
  • \n
  • Kubeconfig: /root/.kube/rke2.yaml on noc1 or WSL
  • \n
  • SSH: ed25519 key auth (root)
  • \n
\n
\n
\n
RKE2 Nodes (Bare-Metal)
\n
    \n
  • rke2-server: rke2-server.iamworkin.lan (i7-1260P / 64GB, control plane)
  • \n
  • rke2-agent1: rke2-agent1.iamworkin.lan (i7-1260P / 64GB, worker)
  • \n
  • rke2-agent2: rke2-agent2.iamworkin.lan (i5-1340P / 64GB, worker)
  • \n
  • SSH: root@10.0.56.{11,12,13} (ed25519 key)
  • \n
  • Puppet: profile::kubernetes::rke2 on all nodes
  • \n
\n
\n
\n
RKE2 Infrastructure
\n
    \n
  • MetalLB: L2 mode, pool 10.0.56.200-220
  • \n
  • Traefik: v3.3.4, 2 replicas, LB traefik.iamworkin.lan
  • \n
  • Longhorn: Default StorageClass (iSCSI), NAS backup integration
  • \n
  • Namespaces (33): fc-system, fc-tenant-{andrew,matt,dustin,erik,fit}, tenant-{andrew,dustin,erik,fit,flowercore}, traefik-system, metallb-system, argocd, irc, mail, matrix, zabbix, guacamole, gitea, teamspeak, onepassword-system, cert-manager, telephony, monitoring, selenium, agent-zero, intranet, pki, noc-proxy, longhorn-system, kube-system, calico-system, tigera-operator
  • \n
  • IngressRoutes: 44 Traefik routes (internal + Cloudflare public)
  • \n
  • PVCs: 17 persistent volumes, ~69 Gi total (Longhorn iSCSI, NAS backup)
  • \n
  • ArgoCD: 22 apps via bluejay-infra ApplicationSet (all Healthy — includes agent-zero, asterisk, monitoring, voice, 5 tenant landing pages, guacamole, mail, matrix, IRC, telephony, Gitea, Zabbix, PKI, intranet, noc-services)
  • \n
  • 1Password: Operator v1.11.0 in onepassword-system, 7 CRDs syncing
  • \n
  • Cloudflare Origin Certs: *.flowercore.io + *.iamwork.in (15-year RSA) deployed across 8 namespaces
  • \n
\n
\n
\n

Cluster Resource Usage (2026-03-21)

\n\n\n\n\n\n\n\n
NodeCPUMemoryPodsRole
rke2-server866m (5%)19,293 Mi (30%)~40Control plane + worker
rke2-agent1616m (3%)20,905 Mi (32%)~40Worker
rke2-agent21,430m (8%)17,517 Mi (27%)~40Worker (Selenium + telephony)
\n
Capacity: 192 GB total RAM (64 GB/node), ~30% utilized. Selenium Grid (4 pods) + ArgoCD (7 pods) + Longhorn (29 pods) are the biggest consumers. Prometheus at 10 Gi retention (90 days). All stateful workloads backed up to BlueJayNAS via Longhorn NFS.
\n\n

pfSense Static Routes (K8s)

\n\n\n\n\n\n\n
DestinationGatewayPurpose
10.42.0.0/16rke2-server.iamworkin.lan (rke2-server)Pod CIDR routing
10.43.0.0/16rke2-server.iamworkin.lan (rke2-server)Service CIDR routing
\n
\n\n\n
\n

NOC Services (noc1)

\n
\n
\n
noc1 Host
\n
    \n
  • IP: noc1.iamworkin.lan
  • \n
  • SSH: root@pfsense.iamworkin.lan0
  • \n
  • Password: \U0001F510 noc1
  • \n
  • OS: openSUSE Leap Micro 6.2 (immutable)
  • \n
  • CPU: Intel Celeron N5105 (4C/4T)
  • \n
  • RAM: 32 GB
  • \n
  • Disk: 1TB NVMe (929GB free)
  • \n
  • Runtimes: Podman 5.4.2, K3s v1.34.5
  • \n
\n
\n
\n

Service Directory

\n\n\n\n\n\n\n\n\n\n\n\n\n
ServiceURLPortCredentialsStatus
Cockpithttps://cockpit.iamworkin.lan443 (Traefik)\U0001F510 noc1Online
Prometheushttps://prometheus.iamworkin.lan
Also: https://prometheus.iamworkin.lan (noc1 direct)
443 (Traefik)No auth (90-day retention, 11 targets)Online
Grafanahttps://grafana.iamworkin.lan
Also: https://grafana.iamworkin.lan (noc1 direct)
443 (Traefik)\U0001F510 GrafanaOnline
Node Exporterhttp://noc1.iamworkin.lan:91009100Metrics onlyOnline
SNMP Exporterhttp://noc1.iamworkin.lan:91169116pfSense + NAS + Switch + Printer SNMP scraperOnline
step-ca ACMEhttps://acme.iamworkin.lan:94439443\U0001F510 step-caOnline
1Password Connecthttp://op-connect.iamworkin.lan:81808180/8181API token authOnline
Puppet Servernoc1:81408140OpenVox Server 8.12 (Podman)Online
\n

RKE2 Services (22 ArgoCD Apps — All Healthy)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ServiceURLMetalLB / PortCredentialsStatus
ArgoCDhttps://argocd.iamworkin.lan443 (via Traefik)\U0001F510 ArgoCDOnline
Traefiktraefik.iamworkin.lan:8080MetalLB .200 — 80/443/8080/6667/6697\U0001F510 Traefik DashboardOnline
Giteahttps://gitea.iamworkin.lan.201 SSH:22 — HTTPS via Traefik\U0001F510 GiteaOnline
Guacamolehttps://guac.iamworkin.lan443 (via Traefik)\U0001F510 Apache GuacamoleOnline
UnrealIRCd + Anopeirc.iamworkin.lan:6697 (TLS).200 — 6667/6697/8067\U0001F510 IRC ServicesOnline
Zabbixhttps://zabbix.iamworkin.lan.203 trapper — Web via Traefik\U0001F510 Zabbix MonitoringOnline
Mail (docker-mailserver)mail.iamworkin.lan.202 — SMTP 25/465/587\U0001F510 Mail ServerOnline
Matrix Synapsehttps://matrix.iamworkin.lan443 (via Traefik)\U0001F510 Matrix SynapseOnline
Element Webhttps://element.iamworkin.lan443 (via Traefik)Uses Matrix accountOnline
TeamSpeakts.iamworkin.lan.205 — 9987/UDP, 30033, 10011\U0001F510 TeamSpeakOnline
FlowerCore Landingflowercore.io443 (via Traefik, Cloudflare)Static pageOnline
PKI Webhttps://pki.iamworkin.lan443 (via Traefik)Public (CRL/certs)Online
Intranethttps://intranet.iamworkin.lan443 (via Traefik)Static pageOnline
Snappymailhttps://mail-web.iamworkin.lan443 (via Traefik)\U0001F510 SnappymailOnline
Telephonyhttps://telephony.iamworkin.lan5100 (via Traefik + Cloudflare)\U0001F510 TelephonyOnline
Asterisk PBXasterisk.iamworkin.lan:5060.207 — SIP 5060/UDP, RTP 10000-200004 PJSIP ext, Twilio trunkOnline
Agent Zerohttps://agent-zero.iamworkin.lan443 (via Traefik)\U0001F510 Agent ZeroOnline
1Password OperatorIn-cluster onlyonepassword-systemConnect tokenOnline
Selenium Gridhttps://selenium.iamworkin.lan443 (via Traefik)Hub + 2 Chrome + 1 FirefoxOnline
FlowerCore.Print.Webhttp://print.iamworkin.lan:52005200 (edge2 direct)12 pages, 9 symbologies, 10 MCPOnline
PiManager (piez)http://piez.iamworkin.lan:50005000 (piez direct)GPIO, I2C, SPI, 20 MCPOnline
PiManager (pirelay)http://pirelay.iamworkin.lan:51005100 (pirelay direct)4-ch relay, schedulingOnline
Frigate NVRhttp://edge1.iamworkin.lan:50005000 (edge1 direct)\U0001F510 FrigateOnline
\n

Monitoring

\n
\n
\n
Prometheus (noc1)
\n
    \n
  • Targets: 15 scrape jobs (node-exporter: noc1 + 3 RKE2 + 2 edge + piez + pirelay, SNMP: pfSense + Cloud Key + Switch + NAS + Printer, Blackbox: 4 AI stack probes, self)
  • \n
  • Alert Rules: 8 (NodeDown, PfSenseDown, HighCPU, HighMemory, DiskSpaceLow, +3)
  • \n
  • Config: /opt/monitoring/prometheus/prometheus.yml
  • \n
  • Reload: podman kill -s SIGHUP prometheus
  • \n
\n
\n
\n
Grafana (noc1:3000)
\n
    \n
  • Version: v12.4.0
  • \n
  • Dashboards: BlueJay Network Overview, Node Exporter Full (#1860), BlueJay Edge Nodes, BlueJay Operations (Prometheus+Zabbix unified) — all in BlueJay folder
  • \n
  • Datasources: Prometheus (http://localhost:9090), Zabbix (alexanderzobnin-zabbix-datasource v6.2.1)
  • \n
\n
\n
\n
Zabbix (RKE2) — 13 Hosts
\n
    \n
  • Agent Hosts (8): noc1, rke2-server, rke2-agent1, rke2-agent2, edge1, edge2, piez, pirelay
  • \n
  • SNMP Hosts (3): pfSense, UniFi Switch, BlueJayNAS (DS1621+)
  • \n
  • SNMP Host (1): Epson ET-3750 EcoTank
  • \n
  • Local (1): Zabbix server self-check
  • \n
  • Agent Version: Zabbix Agent 2 v7.0.22–7.2.15 on all 8 Linux nodes
  • \n
  • Passive checks: Server= includes MetalLB VIP + RKE2 node IPs + pod CIDR
  • \n
  • Note: Mac Mini (macOS) pending Zabbix agent setup
  • \n
\n
\n
\n

Pi Fleet Services (FlowerCore.PiManager)

\n\n\n\n\n\n\n
DeviceURLPortCapabilitiesStatus
piez (Pi 4)http://piez.iamworkin.lan:50005000GPIO, I2C, SPI, Expanders (MCP23017/PCF8574/74HC595) — 10 pages, 35 API, 20 MCPOnline
pirelay (Pi 3)http://pirelay.iamworkin.lan:510051004-ch relay (KS0212, active-LOW), scheduling, usage tracking — 8 pages, relay APIOnline
\n
PiManager: Unified .NET 10 service deployed to both Pi nodes with different ASPNETCORE_ENVIRONMENT overlays. Config-driven capabilities — same binary, different features per device. Supports relay presets: ks0212-4ch, walfront-16ch, sainsmart-8ch. API docs at /scalar/v1 on each node.
\n\n

Guacamole Connection Groups (14 connections)

\n\n\n\n\n\n\n\n\n
GroupConnectionsProtocol
Kubernetes (3)rke2-server, rke2-agent1, rke2-agent2SSH
Network Devices (4)pfSense, UniFi Cloud Key, Synology WiFi (SRM), BlueJayNASSSH
Servers (3)noc1, Mac Mini (SSH), Mac Mini (VNC)SSH/VNC
Edge Nodes (4)edge1 (Pi 5 + AI), edge2 (Pi 4), piez (Pi 4 + EZ Connect), pirelay (Pi 3 + Relay)SSH
\n
Guacamole credentials: All connection passwords are stored in the Guacamole MySQL database (synced from 1Password). Access at https://guac.iamworkin.lan\U0001F510 Apache Guacamole
\n
\n\n\n
\n

VPN & Security

\n
OpenVPN Status: 8 servers configured and operational. Bound to tenant VIPs (.17, .19, .21, .23, .25). Each tenant has TUN (L3 routed) and TAP (L2 bridged) instances on ports 1194/1195 UDP.
\n

OpenVPN Configuration

\n\n\n\n\n\n\n\n\n\n
TenantVIPTUN PortTAP PortTunnel (TUN)Tunnel (TAP)VLAN
ANDREW.171194/UDP1195/UDP10.0.68.0/2710.0.68.128/2760
MATT.191194/UDP1195/UDP10.0.68.32/2710.0.68.160/2761
DUSTIN.211194/UDP1195/UDP10.0.68.64/2710.0.68.192/2762
ERIK.231194/UDP1195/UDP10.0.68.96/2710.0.68.224/2763
FIT.251194/UDP1195/UDP10.0.69.0/2710.0.69.128/2769
\n

VPN Certificate Infrastructure

\n\n\n\n\n\n\n\n\n\n
ComponentDetails
CABlueJay VPN CA (4096-bit RSA, SHA-256, 10-year)
Server Certs8 (one per VPN instance, 2048-bit RSA)
Client Certs4 (one per tenant, 2048-bit RSA)
TLS AuthShared HMAC key across all servers
Data CiphersAES-256-GCM, AES-128-GCM, CHACHA20-POLY1305
\n

IPsec Site-to-Site (Planned)

\n\n\n\n\n\n\n
TunnelLocalRemotePhase 1Phase 2 SAs
Matt.29 (pfSense WAN)Matt's public IPIKEv2, AES-256-GCM, DH 14+MATT (10.0.61.0/24) + PROD (10.0.57.0/24)
Dustin.29 (pfSense WAN)Dustin's public IPIKEv2, AES-256-GCM, DH 14+DUSTIN (10.0.62.0/24) + PROD (10.0.57.0/24)
\n

Security Policies

\n
\n
\n
Cloudflare Protection
\n
    \n
  • SSL Mode: Full (strict) on all 6 zones
  • \n
  • Origin Certs: *.flowercore.io + *.iamwork.in (15-year RSA), deployed across 8 K8s namespaces
  • \n
  • HSTS: Enabled on all zones
  • \n
  • Min TLS: 1.2
  • \n
  • Anti-spoofing: null MX, SPF -all, DMARC reject on non-email domains
  • \n
  • Cloudflare-only inbound: Port forwards for 80/443 restrict source to Cloudflare IP ranges
  • \n
\n
\n
\n
SSH Key Policy
\n
    \n
  • Key Type: ed25519 (deployed to all 9 physical nodes)
  • \n
  • WSL Key: stoltz@IAMWORKIN-WS — deployed to noc1, rke2-server, rke2-agent1, rke2-agent2, edge1, edge2, piez, pirelay, Mac Mini
  • \n
  • noc1 Key: noc1-root + rke2@bluejay — management keys for remote nodes
  • \n
  • Root Login: Key-only (PermitRootLogin without-password)
  • \n
  • RKE2 Nodes: SELinux enforcing, chcon -t ssh_home_t on authorized_keys
  • \n
  • Last verified: 2026-03-21 (all 9 nodes confirmed)
  • \n
\n
\n
\n
Network Security Rules
\n
    \n
  • Forced DNS: HOME/WORK/SCHOOL/GUEST block port 53 except to gateway
  • \n
  • Blocked SMTP: Outbound 25/465/587 on HOME/WORK/SCHOOL/GUEST
  • \n
  • Firewall Policy: Deny-all default, explicit allow per VLAN
  • \n
  • Tenant Isolation: Tenants fully isolated from each other, only PROD + DNS + NAS + internet
  • \n
\n
\n
\n

PKI Hierarchy

\n\n\n\n\n\n\n\n\n\n
CAStatusPurpose
Root CA (IAmWorkin ACME CA)OperationalTrust anchor, ECDSA P-256, expires 2036
ACME CA (step-ca on noc1)OperationalAutomated cert issuance via ACME protocol
Network CAPlannedSwitch, AP, pfSense device certs
Windows AD CS CAPlannedDomain-joined machine/user certs
Internal Services CAPlannedK8s service mesh, inter-service mTLS
\n
\n\n\n
\n

Remote Access — Blue Jay Gateway

\n
Apache Guacamole with Blue Jay branding, 1Password vault integration, K8s exec, and embedded panels. All credentials resolved from 1Password at connection time — no passwords stored in Guacamole.
\n\n
\n
\n
Guacamole Web UI
\n
    \n
  • URL: guac.iamworkin.lan
  • \n
  • Version: 1.6.0 + Blue Jay branding
  • \n
  • Admin: Guacamole
  • \n
  • K8s: guacamole namespace
  • \n
  • Ingress: Traefik → guacamole:8080 (WebSocket)
  • \n
  • ArgoCD: infra-guacamole
  • \n
\n
\n
\n
Extensions
\n
    \n
  • Blue Jay Branding — Full dark theme, custom login, logo
  • \n
  • 1Password Vault${VAULT_PASSWORD} token resolution
  • \n
  • TOTP MFA — Required for all users
  • \n
  • Auth Ban — 5 failures = 5min IP ban
  • \n
  • JSON Auth — Signed tokens for embedded panels
  • \n
  • Time Restrict — Per-connection time windows
  • \n
  • Recording Storage — NFS (Synology) playback
  • \n
  • Display Statistics — Performance metrics
  • \n
\n
\n
\n
1Password Integration
\n
    \n
  • Connect URL: onepassword-connect:8080 (K8s internal)
  • \n
  • Vault: IAmWorkin (qaphopopkryhbg353ukzhhuqoq)
  • \n
  • Token: Via OnePasswordItem CRD
  • \n
  • Rotation: Automatic — change in 1Password, Guacamole picks up on next connect
  • \n
  • Cache TTL: 5 minutes
  • \n
\n
\n
\n
Session Recording
\n
    \n
  • Storage: NFS on Synology (/volume1/guacamole/recordings)
  • \n
  • PVC: guacamole-recordings-pvc (50 Gi)
  • \n
  • Format: Guacamole native (playable in browser)
  • \n
  • Retention: Linked to connection history
  • \n
\n
\n
\n\n

Connection Inventory (${VAULT_*} tokens — no hardcoded passwords)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ConnectionProtocolHostVLAN1Password Item
MGMT (VLAN 56) — Infrastructure
pfSense — SSHSSHpfsense.iamworkin.lan56pfSense Admin
Cloud Key — SSHSSHunifi.iamworkin.lan56UniFi CloudKey
UniFi Switch — SSHSSHswitch.iamworkin.lan56UniFi CloudKey
noc1 — SSHSSHnoc1.iamworkin.lan56noc1 Root SSH
rke2-server — SSHSSHrke2-server.iamworkin.lan56RKE2 Server
rke2-agent1 — SSHSSHrke2-agent1.iamworkin.lan56RKE2 Agent 1
rke2-agent2 — SSHSSHrke2-agent2.iamworkin.lan56RKE2 Agent 2
PROD (VLAN 57) — Production
edge1 (Pi 5) — SSHSSHedge1.iamworkin.lan57Edge1 Pi5 SSH
edge2 (Pi 4) — SSHSSHedge2.iamworkin.lan57Edge2 Pi4 SSH
Mac Mini — SSHSSHmacmini.iamworkin.lan57Mac Mini
Mac Mini — VNCVNCmacmini.iamworkin.lan57Mac Mini
HOME (VLAN 58) — Home Network
Synology NAS — SSHSSHnas.iamworkin.lan58Synology NAS
Synology WiFi — SSHSSHwifi.iamworkin.lan58Synology SRM
piez (Pi 4) — SSHSSHpiez.iamworkin.lan58PiEZ SSH
pirelay (Pi 3) — SSHSSHpirelay.iamworkin.lan58PiRelay SSH
Kubernetes — Pod Exec (auto-synced every 2min)
argocd-serverK8Skubernetes.default.svc(ServiceAccount)
gitea-0K8Skubernetes.default.svc(ServiceAccount)
asteriskK8Skubernetes.default.svc(ServiceAccount)
zabbix-serverK8Skubernetes.default.svc(ServiceAccount)
synapseK8Skubernetes.default.svc(ServiceAccount)
unrealircdK8Skubernetes.default.svc(ServiceAccount)
\n\n

Embedded Panel (Quick SSH)

\n
The embedded panel below uses guacamole-common-js to connect directly to Guacamole's tunnel servlet. Requires authentication to guac.iamworkin.lan first.
\n
\n
\n
\n \n noc1 — SSH Terminal\n Open in Guacamole →\n
\n
\n

Connect via Blue Jay Remote Access to use the embedded terminal.
\n Requires bluejay-guac-embed.js and guacamole-common-js

\n
\n
\n
\n\n

Deployment Details

\n\n\n\n\n\n\n\n\n
ComponentImageReplicasResources
guacamole (Tomcat)fc-guacamole:1.6.0-bluejay1200m-1 CPU, 512Mi-1Gi
guacd (C proxy)guacamole/guacd:1.6.01200m-2 CPU, 256Mi-1Gi
MySQL 8mysql:8.01 (StatefulSet)100m-500m CPU, 256-512Mi
K8s Sync CronJobbitnami/kubectl:1.34every 2minminimal
\n\n

Files Reference

\n\n\n\n\n\n\n\n\n\n\n\n\n
ArtifactPath
Design plandocs/infrastructure/guacamole-customization-plan.md
K8s manifestsk8s/guacamole/*.yaml
Branding extensionk8s/guacamole/extensions/bluejay-branding/
1Password vault extensionk8s/guacamole/extensions/1password-vault/
Embed libraryk8s/guacamole/scripts/bluejay-guac-embed.js
Dockerfilek8s/guacamole/Dockerfile
Bootstrap scriptk8s/guacamole/scripts/bootstrap-connections.sh
Build/deploy scriptk8s/guacamole/scripts/build-image.sh
\n
\n\n\n
\n

Edge Nodes

\n
\n
\n
edge1 — Raspberry Pi 5 + Hailo AI
\n
    \n
  • IP: edge1.iamworkin.lan (PROD VLAN 57)
  • \n
  • SSH: stoltz@edge1.iamworkin.lan
  • \n
  • Password: \U0001F510 Edge1 Pi5 SSH
  • \n
  • Hardware: Pi 5 16GB + Hailo-10H 40 TOPS (AI HAT+ 2)
  • \n
  • OS: Debian 13 (trixie) aarch64
  • \n
  • PCIe: Gen 3 x1 (8.0 GT/s)
  • \n
  • Power: 27W USB-C
  • \n
  • .NET SDK: 10.0.103
  • \n
  • GitHub Runner: v2.332.0 (labels: pi5, hailo)
  • \n
  • Node Exporter: :9100
  • \n
  • Puppet: profile::edge_ai
  • \n
  • Zabbix Agent: v7.2.15 (passive, port 10050)
  • \n
  • Switch Port: 13
  • \n
  • Disk: 93% (2.0GB free)
  • \n
\n
\n
\n
edge2 — Raspberry Pi 4 (Argon ONE)
\n
    \n
  • IP: edge2.iamworkin.lan (PROD VLAN 57)
  • \n
  • SSH: stoltz@edge2.iamworkin.lan
  • \n
  • Password: \U0001F510 Edge2 Pi4 SSH
  • \n
  • Hardware: Pi 4 Model B 4GB, Argon ONE case
  • \n
  • OS: Debian 13 (trixie) aarch64
  • \n
  • Fan Control: argononed.service (55°C=10%, 60°C=55%, 65°C=100%)
  • \n
  • .NET SDK: 10.0.103
  • \n
  • Print Service: LIVE FlowerCore.Print.Web :5200 (12 pages, 9 symbologies, AI barcode)
  • \n
  • GitHub Runners: v2.332.0 — MySQL (edge2-mysql), PHP (edge2-php)
  • \n
  • Node Exporter: :9100
  • \n
  • Puppet: profile::edge_runner
  • \n
  • Zabbix Agent: v7.2.15 (passive, port 10050)
  • \n
  • Switch Port: 11
  • \n
  • Guacamole: SSH connection in Edge Nodes group
  • \n
\n
\n
\n
piez — Raspberry Pi 4 + EZ Connect
\n
    \n
  • IP: piez.iamworkin.lan (HOME VLAN 58, WiFi)
  • \n
  • SSH: stoltz@piez.iamworkin.lan
  • \n
  • Password: \U0001F510 piez SSH
  • \n
  • Hardware: Pi 4 Model B 4GB + Pi EZ Connect board
  • \n
  • OS: Debian 13 (trixie) aarch64
  • \n
  • Role: GPIO prototyping, breadboard dev, I2C/SPI sensors
  • \n
  • .NET SDK: 10.0.201
  • \n
  • Web: LIVE FlowerCore.PiManager :5000 (10 pages, 35 API endpoints, 20 MCP tools)
  • \n
  • API Docs: Scalar :5000/scalar/v1
  • \n
  • Capabilities: GPIO, I2C, SPI, Expanders (MCP23017/PCF8574/74HC595)
  • \n
  • Node Exporter: :9100
  • \n
  • Zabbix Agent: v7.0.22 (passive, port 10050)
  • \n
  • Guacamole: SSH connection in Edge Nodes group
  • \n
  • Dashboard: piez-prototyping.html
  • \n
\n
\n
\n
pirelay — Raspberry Pi 3 + 4-Ch Relay
\n
    \n
  • IP: pirelay.iamworkin.lan (HOME VLAN 58)
  • \n
  • SSH: stoltz@pirelay.iamworkin.lan
  • \n
  • Password: \U0001F510 pirelay SSH
  • \n
  • Hardware: Pi 3 Model B v1.2, 906 MB RAM + Keyestudio KS0212 4-channel relay shield
  • \n
  • OS: Debian 13 (trixie) aarch64
  • \n
  • Role: Relay controller, home automation prototyping
  • \n
  • Web: LIVE FlowerCore.PiManager :5100 (relay preset: ks0212-4ch)
  • \n
  • API Docs: Scalar :5100/scalar/v1
  • \n
  • GPIO (BCM, active-LOW): CH1=GPIO4, CH2=GPIO22, CH3=GPIO6, CH4=GPIO26
  • \n
  • Relay Ratings: 10A @ 250VAC / 30VDC per channel
  • \n
  • Node Exporter: :9100
  • \n
  • Zabbix Agent: v7.0.22 (passive, port 10050)
  • \n
  • Guacamole: SSH connection in Edge Nodes group
  • \n
  • Dashboard: relay-controller.html
  • \n
\n
\n
\n
Mac Mini (Build/Test Node)
\n
    \n
  • IP: macmini.iamworkin.lan (PROD VLAN 57)
  • \n
  • SSH: bluejay@macmini.iamworkin.lan
  • \n
  • Credentials: \U0001F510 Mac Mini
  • \n
  • VNC: vnc://macmini.iamworkin.lan:5900\U0001F510 Mac Mini
  • \n
  • Hardware: Apple M1, 16GB RAM, 926GB SSD
  • \n
  • OS: macOS 26.3.1 (Darwin 25.3.0)
  • \n
  • Role: Xcode builds, Selenium Grid node, automated browser/app testing
  • \n
  • Guacamole: SSH + VNC connections in Servers group
  • \n
\n
\n
\n\n

Edge2 — Print Service

\n
\n
\n
FlowerCore.Print.Web
\n
    \n
  • URL: http://print.iamworkin.lan:5200
  • \n
  • Pages: 12 Blazor pages (barcode gen, batch print, product cache, AI labels)
  • \n
  • Symbologies: 9 (Code128, EAN-13, QR, DataMatrix, ITF-14, UPC-A/E, Code39, Codabar)
  • \n
  • Features: Product cache DB, AI label generation (Ollama on Pi), batch barcodes, combo labels
  • \n
  • MCP Tools: 10 tools for programmatic barcode/label generation
  • \n
  • Thermal Printer: Connected Epson ET-3750 (printer.iamworkin.lan)
  • \n
  • systemd: flowercore-print.service (auto-start)
  • \n
\n
\n
\n\n

Edge1 AI & Speech Services

\n
\n
\n
Ollama (LLM Inference)
\n
    \n
  • API: http://edge1.iamworkin.lan:11434
  • \n
  • Model: qwen2.5-coder:7b (4.7GB Q4_K_M)
  • \n
  • Managed by: profile::edge::ollama (Puppet)
  • \n
  • Firewall: nftables port 11434 from MGMT+PROD
  • \n
  • Note: SD card 95% full — one model max
  • \n
\n
\n
\n
Piper TTS (Text-to-Speech)
\n
    \n
  • Version: piper-tts 1.4.1 in ~/piper-env venv
  • \n
  • Voices: en_US-amy-low (16kHz) + en_US-amy-medium (22kHz)
  • \n
  • Performance: RTF 0.10 (10x real-time), 222ms latency (short)
  • \n
  • CPU Usage: 3/4 cores (271%)
  • \n
  • Note: 16kHz matches G.711 natively for telephony
  • \n
\n
\n
\n
Hailo Whisper STT (Speech-to-Text)
\n
    \n
  • Model: Whisper-Base HEF (131MB, v5.1.1)
  • \n
  • Path: /opt/hailo-models/Whisper-Base.hef
  • \n
  • Performance: RTF 0.05-0.11 (10-18x real-time)
  • \n
  • Model Load: 1.2s cold start
  • \n
  • Multi-process: VDevice for coexistence with Frigate
  • \n
\n
\n
\n
Speech Pipeline Service
\n
    \n
  • API: http://edge1.iamworkin.lan:8500
  • \n
  • Endpoints: POST /tts, POST /stt, GET /health
  • \n
  • User: speech in hailo group
  • \n
  • Managed by: profile::edge::speech_pipeline (Puppet)
  • \n
  • Firewall: nftables port 8500 from MGMT+PROD
  • \n
\n
\n
\n
Twilio Voice Bridge (PoC)
\n
    \n
  • Location: /opt/twilio-bridge/ on edge1
  • \n
  • WebSocket: :8765 • TwiML: :8766
  • \n
  • Cloudflare Tunnel: bluejay-voice (3ddfa567-b0a7-40cb-9c57-7f20f3ec3637)
  • \n
  • URLs: voice.bluejay.dev (TwiML), voice-ws.bluejay.dev (WS)
  • \n
  • Services: cloudflared-tunnel on noc1, twilio-bridge + twilio-twiml on edge1
  • \n
  • Status: PoC\ - \ — STT fixed, TTS stream API mismatch
  • \n
\n
\n
\n
\n\n\n
\n

Storage

\n
\n
\n
BlueJayNAS — Synology DS1621+
\n
    \n
  • IP: nas.iamworkin.lan (HOME VLAN 58, switch port 14)
  • \n
  • DNS: nas.iamworkin.lan, synology.iamworkin.lan
  • \n
  • DSM: https://nas.iamworkin.lan:5001 (v7.3.2-86009 Update 1)
  • \n
  • SSH: bluejay@nas.iamworkin.lan
  • \n
  • Credentials: \U0001F510 BlueJayNAS
  • \n
  • Model: DS1621+ (6-bay, AMD Ryzen V1500B)
  • \n
  • Storage: 9.1 TB Btrfs (RAID), ~7.8 TB free
  • \n
  • MAC: 00:11:32:f2:43:6b
  • \n
  • TLS Cert: ca.iamworkin.lan (step-ca ACME, expires 2026-06-03)
  • \n
  • NFS Domain: private.iamwork.in
  • \n
  • 2FA: TOTP enabled on DSM
  • \n
\n
\n
\n
NFS Exports & Services
\n
    \n
  • Longhorn Backup: nfs://nas.iamworkin.lan:/volume1/NetBackup/longhorn-backups
  • \n
  • Kubernetes Shared: /volume1/kubernetes (NFS mount for PVCs)
  • \n
  • Selenium Screenshots: /volume1/selenium/screenshots (AAT visual tests via PVC)
  • \n
  • Selenium Videos: /volume1/selenium/videos (test recordings)
  • \n
  • NFS Permissions: RKE2 nodes rke2-server/agent1/agent2 (MGMT VLAN cross-VLAN rule)
  • \n
  • Ports: NFS (2049), iSCSI (3260, no targets yet), DSM API (5001), SSH (22), SNMP (161)
  • \n
  • pfSense Rule: RKE2 → NAS on 2049/3260/5001
  • \n
\n
\n
\n
Monitoring & Security
\n
    \n
  • SNMP: v2c community \U0001F510 SNMP
  • \n
  • Zabbix Host: BlueJayNAS (ID 10678) — Linux by SNMP template
  • \n
  • Prometheus: SNMP scrape via snmp-exporter (synology module)
  • \n
  • Auto Block: Enabled (brute-force protection)
  • \n
  • DSM Firewall: DO NOT ENABLE — synofirewall segfaults on 7.3.2, causes lockout
  • \n
  • admin account: ENABLED — never disable (breaks all admin-group privileges)
  • \n
  • SSH: Keep PasswordAuthentication yes (disabling breaks sudo/PAM)
  • \n
  • Guacamole: SSH connection in Network Devices group
  • \n
\n
\n
\n
Recovery Notes
\n
    \n
  • admin disabled recovery: Physical RESET button (4s hold, 1 beep) + power cycle
  • \n
  • Firewall lockout: Physical RESET (same procedure)
  • \n
  • Security hardening: Use pfSense cross-VLAN rules, NOT DSM-level firewall/SSH hardening
  • \n
  • CLI tools: /usr/syno/bin/synopkg, /usr/syno/sbin/synouser, /usr/syno/sbin/synogroup
  • \n
  • DSM API: https://nas.iamworkin.lan:5001/webapi/entry.cgi — SYNO.API.Auth + otp_code for 2FA
  • \n
\n
\n
\n\n

Longhorn Persistent Volume Claims (17 PVCs, ~69 Gi)

\n
Longhorn → NAS Backup: Longhorn is the default StorageClass on the RKE2 cluster (iSCSI). All 17 PVCs backed up to BlueJayNAS via NFS. Daily backups at 02:00 UTC (retain 14 days), hourly snapshots (retain 24).
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NamespacePVCSizePurpose
monitoringprometheus-data + grafana-data12 GiPrometheus TSDB (90-day retention) + Grafana dashboards/DB
zabbixzabbix-postgres-data10 GiZabbix PostgreSQL (13 hosts, history/trends)
giteagitea-shared-storage10 GiGit repositories, LFS objects, attachments
telephonyasterisk-data + telephony-data10 GiAsterisk PBX config + FlowerCore.Telephony DB
matrixmatrix-postgres-data + synapse-data7 GiMatrix Synapse PostgreSQL + media store
mailmail-data + mail-state6 Gidocker-mailserver (Postfix queues, Dovecot mail)
agent-zeroagent-zero-data + knowledge6 GiAgent Zero persistent data + FAISS knowledge base
guacamoleguac-mysql-data5 GiGuacamole MySQL (14 connections, session history)
ircanope-data + unrealircd-data2 GiIRC services DB (channels, nicks) + UnrealIRCd config
teamspeakteamspeak-data1 GiTeamSpeak virtual server config + file transfers
\n\n\n\n\n\n\n\n\n\n
ComponentDetail
Storage BackendLonghorn (iSCSI, default StorageClass, 3 replicas per volume)
Backup Targetnfs://nas.iamworkin.lan:/volume1/NetBackup/longhorn-backups
Backup ScheduleDaily at 02:00 UTC (retain 14 days), hourly snapshots (retain 24)
RKE2 Requirementiscsid enabled on all nodes (systemctl enable --now iscsid)
Managed byPuppet profile::kubernetes::rke2 (prerequisites, kernel modules, sysctl)
\n\n

Synology CSI Driver (Pending)

\n
Status: Helm repo added, deployment pending. Will enable dynamic PVC provisioning directly from Synology NFS/iSCSI.
\n\n\n\n\n\n\n\n\n\n
ComponentDetail
DriverSynologyOpenSource/synology-csi v1.2.1
Helm Chartchristian-schlichtherle, v0.11.0
ProtocolsNFS, iSCSI, SMB
Service Accountk8s-csi (UID 1032) on BlueJayNAS
1Password\U0001F510 Synology CSI creds
\n
\n\n\n
\n

WiFi Networks

\n
Credentials: All WiFi passwords are stored in the IAmWorkin vault on 1Password. To connect a device, open the 1Password app, find the WiFi entry, and scan the QR code from there. Passwords are not stored in this page for security.
\n
QR Code Connection: Open 1Password → search for the SSID name → tap “Show QR Code” → scan with your device camera. The QR code encodes the full WIFI:T:WPA;S:{SSID};P:{PASSWORD};;; connection string.
\n\n
\n \n
\n
\n
BlueJay-Home
\n
HOME (VLAN 58)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-Home\n
\n
\n VLAN\n 58 (untagged on AP)\n
\n
\n Security\n WPA2/WPA3\n
\n \n
\n Purpose\n Home network — personal / family use\n
\n
\n Bandwidth\n 800 / 800 Mbps\n
\n
\n Public IP\n 74.40.140.29\n
\n
\n
\n\n \n
\n
\n
BlueJay-Employee
\n
EMPLOYEE (VLAN 59)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-Employee\n
\n
\n VLAN\n 59\n
\n
\n Security\n WPA2/WPA3\n
\n \n
\n Purpose\n Employee network — staff device access\n
\n
\n Bandwidth\n 500 / 500 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n\n \n
\n
\n
BlueJay-Work
\n
WORK (VLAN 64)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-Work\n
\n
\n VLAN\n 64\n
\n
\n Security\n WPA2/WPA3\n
\n \n
\n Purpose\n Work network — business devices\n
\n
\n Bandwidth\n 500 / 500 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n\n \n
\n
\n
BlueJay-School
\n
SCHOOL (VLAN 65)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-School\n
\n
\n VLAN\n 65\n
\n
\n Security\n WPA2/WPA3\n
\n \n
\n Purpose\n School network — student devices\n
\n
\n Bandwidth\n 200 / 200 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n\n \n
\n
\n
BlueJay-Guest
\n
GUEST (VLAN 66)
\n
\n
\n
\n \n \n \n \n \n Open network — no password required\n
\n
\n
\n
\n SSID\n BlueJay-Guest\n
\n
\n VLAN\n 66\n
\n
\n Security\n Open / Captive Portal\n
\n
\n Password\n None (open)\n
\n
\n Purpose\n Guest WiFi — fully isolated, NAT only\n
\n
\n Bandwidth\n 100 / 50 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n
\n\n

WiFi Access Point

\n
\n
\n
Synology RT6600AX (AP Mode)
\n \n
\n
\n\n
Network Isolation: Each SSID maps to a separate VLAN with independent firewall rules and bandwidth limits. GUEST is fully isolated with NAT — no access to internal resources. EMPLOYEE, WORK, and SCHOOL share public IP .28 with traffic shaping.
\n
\n\n\n
\n

Credentials & 1Password

\n
\n
\n
1Password Connect Server
\n
    \n
  • API: http://op-connect.iamworkin.lan:8180
  • \n
  • Sync: http://op-connect.iamworkin.lan:8181
  • \n
  • Host: noc1 (Podman containers)
  • \n
  • Status: Online
  • \n
\n
\n
\n
1Password K8s Operator
\n
    \n
  • Namespace: onepassword-system
  • \n
  • Chart: 1password/connect v2.3.0
  • \n
  • Operator: v1.11.0
  • \n
  • Poll Interval: 600s
  • \n
  • Status: Online
  • \n
\n
\n
\n
IAmWorkin Vault
\n
    \n
  • Vault Name: IAmWorkin
  • \n
  • Items: 45+ items (infra credentials + WiFi QR codes + Pi device passwords)
  • \n
  • Rotation: Quarterly (Jan/Apr/Jul/Oct)
  • \n
  • Script: /opt/scripts/rotate-credentials.sh
  • \n
  • Timer: credential-rotation.timer
  • \n
\n
\n
\n\n
All infrastructure credentials are managed in 1Password. The IAmWorkin vault contains credentials for every service listed on this intranet. K8s workloads (Zabbix, Matrix, Guacamole, Mail, IRC, Gitea, ArgoCD) sync secrets automatically via OnePasswordItem CRDs. Credential rotation runs quarterly via systemd timer.
\n\n

K8s Secret Sync (OnePasswordItem CRDs)

\n\n\n\n\n\n\n\n\n\n\n\n
NamespaceSecret NameSource (1Password Item)Status
zabbixzabbix-credentialsZabbix MonitoringSynced
matrixmatrix-credentialsMatrix SynapseSynced
guacamoleguacamole-credentialsApache GuacamoleSynced
mailmail-credentialsMail ServerSynced
ircirc-credentialsIRC ServicesSynced
giteagitea-credentialsGiteaSynced
argocdargocd-credentialsArgoCDSynced
\n\n

Pi Fleet & Edge Node Credentials

\n\n\n\n\n\n\n\n\n\n
DeviceIPUser1Password ItemServices
edge1 (Pi 5)edge1.iamworkin.lanstoltz\U0001F510 Edge1 Pi5 SSHOllama, Piper TTS, Hailo STT, Frigate
edge2 (Pi 4)edge2.iamworkin.lanstoltz\U0001F510 Edge2 Pi4 SSHGitHub Actions runners
piez (Pi 4)piez.iamworkin.lanstoltz\U0001F510 piezPiManager :5000 (GPIO, I2C, SPI)
pirelay (Pi 3)pirelay.iamworkin.lanstoltz\U0001F510 pirelayPiManager :5100 (4-ch relay)
Mac Minimacmini.iamworkin.lanbluejay\U0001F510 Mac MiniSSH + VNC :5900, Xcode builds
\n\n

Credential Rotation

\n
\n
\n
Rotation Script
\n
    \n
  • Path: /opt/scripts/rotate-credentials.sh
  • \n
  • Usage: rotate-credentials.sh {service|all} [--dry-run]
  • \n
  • Services: grafana, guacamole, zabbix, argocd, gitea, snappymail, traefik, matrix, harvester (17/17 complete, all XKCD-style)
  • \n
  • Schedule: Quarterly (1st of Jan/Apr/Jul/Oct at 03:00 UTC)
  • \n
  • Log: /var/log/credential-rotation.log
  • \n
\n
\n
\n
\n\n\n
\n

Planned Services

\n
All previously planned services (Gitea, IRC, Zabbix, ArgoCD, 1Password, Mail, Matrix, TeamSpeak, Guacamole) are now live on RKE2. Remaining planned items are Windows Server VMs and authenticated proxy.
\n\n\n\n\n\n\n\n\n\n
ServiceIPHostRoleStatus
Windows DC110.0.56.20VM (hypervisor TBD)AD Domain Controller (iamworkin.lan)Planned
Windows WAC110.0.56.21VM (hypervisor TBD)Windows Admin CenterPlanned
Windows RDS110.0.57.20VM (hypervisor TBD)Remote Desktop ServicesPlanned
Windows IIS110.0.57.21VM (hypervisor TBD)IIS Web ServerPlanned
Squid Proxy10.0.56.22VM (hypervisor TBD)Authenticated web proxy (Kerberos/LDAP)Planned
\n
\n\n\n
\n

Network Topology

\n
\n\n
Internet
\n
\n
Frontier ONT + NVG468MQ Modem

WAN: 74.32.185.184/28: .17-.29

192.168.254.254 • DMZ to pfSense

\n
\n
pfSense Netgate 4100

WAN: ix3 (.122)LAN: igc0 (802.1Q trunk)

13 VLANs • 13 VIPs • 28 port forwards • DNS/DHCP/NTP/SNMP

\n
\n
UniFi USW-Lite-16-PoE Switch

switch.iamworkin.lan • 16 ports • VLANs 56-67

\n
\n\n
\n
\n
noc1
\n

noc1.iamworkin.lan MGMT

\n

Celeron N5105 • 32GB • K3s + Podman

\n
    \n
  • Grafana :3000
  • Prometheus :9091
  • \n
  • step-ca :9443
  • Cockpit :9090
  • \n
  • Puppet :8140
  • 1Password Connect :8180
  • \n
\n
\n
\n
RKE2 Bare-Metal Cluster
\n

Traefik: traefik.iamworkin.lan MGMT

\n
    \n
  • rke2-server: .11 (i7-1260P/64GB, control plane)
  • \n
  • rke2-agent1: .12 (i7-1260P/64GB, worker)
  • \n
  • rke2-agent2: .13 (i5-1340P/64GB, worker)
  • \n
\n

RKE2 v1.34.5 • Calico • MetalLB • Longhorn • Traefik v3.3.4 • ArgoCD • 11 apps • Asterisk PBX

\n
\n
\n
WiFi (Synology RT6600AX)
\n

wifi.iamworkin.lan HOME

\n
    \n
  • BlueJay-Home (untagged)
  • BlueJay-Employee (VLAN 59)
  • \n
  • BlueJay-Work (VLAN 64)
  • BlueJay-School (VLAN 65)
  • \n
  • BlueJay-Guest (VLAN 66)
  • \n
\n
\n
\n
PROD Nodes
\n

PROD VLAN 57

\n
    \n
  • Mac Mini: macmini.iamworkin.lan (Xcode)
  • \n
  • edge1 Pi5: edge1.iamworkin.lan (Hailo AI)
  • \n
  • edge2 Pi4: edge2.iamworkin.lan (CI runner)
  • \n
\n
\n
\n
HOME Pi Fleet
\n

HOME VLAN 58 • FlowerCore.PiManager

\n
    \n
  • piez Pi4: piez.iamworkin.lan (EZ Connect, GPIO/I2C/SPI) — :5000
  • \n
  • pirelay Pi3: pirelay.iamworkin.lan (KS0212 4-ch relay) — :5100
  • \n
  • Unified PiManager binary • config-driven • node-exporter • Zabbix
  • \n
\n
\n
\n
Network Devices & Storage
\n
    \n
  • Cloud Key: unifi.iamworkin.lan
  • \n
  • BlueJayNAS (DS1621+): nas.iamworkin.lan
  • \n
  • 9.1TB Btrfs • NFS • Longhorn backup • SNMP
  • \n
  • Modem: 192.168.254.254
  • \n
\n
\n
\n
\n
\n\n\n
\n

Domains

\n
\n
17
Registered Domains
\n
1
Internal Domain
\n
1
Blog Hosting (DreamHost)
\n
18
Total Domains
\n
\n\n

FlowerCore Domains

\n\n\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
flowercore.ioFlowerCoreAndrewProduction APICloudflareNamecheap
flowerinsider.xyzFlowerCoreAndrewDev/stagingCloudflareNamecheap
flowerinsider.comFlowerCore CoAndrewCompany siteNamecheapNamecheap
flowerinsider.nlFlowerCore CoAndrewDutch siteNamecheapNamecheap
\n\n

Work Domains

\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
iamwork.inWorkAndrewEmployee portal, IVR, TelephonyCloudflareNamecheap
iamworkin.comWorkAndrewRedirectNamecheapNamecheap
\n\n

Personal & Tenant Domains

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
ackeroni.comErikErikPersonalNamecheapNamecheap
erckak.comErikErikPersonalNamecheapNamecheap
erckak.devErikErikDeveloper portfolioCloudflareNamecheap
digirido.comRandomAndrewDigiKey testingNamecheapNamecheap
timeforta.coDustinDustinPersonalCloudflareNamecheap
shenanjia.comWifeWifePersonal siteNamecheapNamecheap
bluejay.apiPersonal FunAndrewAPI experimentsNamecheapNamecheap
bluejay.devPersonal FunAndrewDev projects, voice bridgeCloudflareNamecheap
jayblue.devPersonal FunAndrewDev projectsNamecheapNamecheap
z.orbRandomAndrewShort URLNamecheapNamecheap
\n\n

Blog & Content Domains

\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
pebbleandpeanut.comBlogAndrewPersonal blogDreamHostNamecheap
pebblesandpeanuts.comBlogAndrewAlt redirectNamecheapNamecheap
\n\n

Internal Domain

\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderNotes
iamworkin.lanInternalAndrewInternal infrastructure, future AD DSpfSense Unbound52+ host overrides + 4 wildcard redirect zones, not publicly registered
\n\n

Cloudflare (6 Zones)

\n
\n
\n
Cloudflare Account
\n
    \n
  • Account: Astoltz@iamwork.in
  • \n
  • Plan: Pro (planned)
  • \n
  • NS: dan.ns.cloudflare.com, frida.ns.cloudflare.com
  • \n
  • All Zones: SSL Full(strict), HSTS, min TLS 1.2
  • \n
  • API Tokens: \U0001F510 Cloudflare API Tokens
  • \n
\n
\n
\n
Active Zones
\n
    \n
  • flowercore.io — Production API, landing page
  • \n
  • iamwork.in — Employee portal, telephony, DDNS
  • \n
  • bluejay.dev — Dev projects, voice bridge
  • \n
  • erckak.dev — Erik developer portfolio
  • \n
  • timeforta.co — Dustin personal
  • \n
  • flowerinsider.xyz — Dev/staging
  • \n
\n
\n
\n

Namecheap API

\n
\n
\n
API Configuration
\n
    \n
  • Base URL: https://api.namecheap.com/xml.response
  • \n
  • API User: astoltz
  • \n
  • API Key: \U0001F510 Namecheap API
  • \n
  • Sandbox URL: https://api.sandbox.namecheap.com/xml.response
  • \n
\n
\n
\n
Dynamic DNS
\n
    \n
  • Hostname: gateway.iamwork.in
  • \n
  • Points to: pfSense WAN IP (auto-updated)
  • \n
  • DDNS: gateway.iamwork.in → pfSense WAN DHCP IP (via Cloudflare API)
  • \n
  • Update Method: pfSense Dynamic DNS client (Cloudflare API token)
  • \n
  • Token: \U0001F510 Cloudflare pfSense Token
  • \n
\n
\n
\n\n

Internal DNS Architecture

\n
Split-Horizon DNS (LIVE): External requests to flowercore.io resolve via Cloudflare to public IP .24 (PROD). Internal requests resolve via pfSense Unbound to K8s MetalLB VIP (10.0.56.200), avoiding NAT hairpin. All internal infrastructure uses iamworkin.lan zone. 4 tenant .lan wildcard redirect zones configured in Unbound (base64-encoded custom_options).
\n\n

Planned IPv6 (ULA)

\n\n\n\n\n\n
PrefixSchemeMethod
fdbc:56:XX::/64XX = VLAN ID (e.g., fdbc:56:56::/64 for MGMT)SLAAC + DHCPv6 (servers), SLAAC-only (clients)
\n
\n\n\n\n\n" -kind: ConfigMap -metadata: - name: intranet-html - namespace: intranet ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: intranet - name: intranet - namespace: intranet -spec: - replicas: 1 - selector: - matchLabels: - app: intranet - template: - metadata: - labels: - app: intranet - spec: - containers: - - image: nginx:alpine - livenessProbe: - httpGet: - path: /healthz - port: 80 - initialDelaySeconds: 5 - periodSeconds: 10 - name: nginx - ports: - - containerPort: 80 - name: http - readinessProbe: - httpGet: - path: /healthz - port: 80 - initialDelaySeconds: 3 - periodSeconds: 5 - resources: - limits: - cpu: 50m - memory: 64Mi - requests: - cpu: 5m - memory: 16Mi - volumeMounts: - - mountPath: /etc/nginx/conf.d/default.conf - name: nginx-conf - subPath: default.conf - - mountPath: /usr/share/nginx/html - name: html - volumes: - - configMap: - name: intranet-nginx-conf - name: nginx-conf - - configMap: - name: intranet-html - name: html ---- -apiVersion: v1 -kind: Service -metadata: - name: intranet - namespace: intranet -spec: - ports: - - name: http - port: 80 - targetPort: 80 - selector: - app: intranet ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: intranet-tls - namespace: intranet -spec: - dnsNames: - - intranet.iamworkin.lan - issuerRef: - kind: ClusterIssuer - name: step-ca-acme - secretName: intranet-tls ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: intranet - namespace: intranet -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`intranet.iamworkin.lan`) - services: - - name: intranet - port: 80 - tls: - secretName: intranet-tls +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/part-of: bluejay-infra + name: intranet +--- +apiVersion: v1 +data: + default.conf: "server {\n listen 80;\n server_name _;\n root /usr/share/nginx/html;\n index index.html;\n\n location / {\n try_files $uri $uri/ =404;\n }\n\n location /healthz {\n access_log off;\n return 200 \"ok\";\n add_header Content-Type text/plain;\n }\n}\n" +kind: ConfigMap +metadata: + name: intranet-nginx-conf + namespace: intranet +--- +apiVersion: v1 +data: + index.html: "\n\n\n\n\nBlue Jay Lab Intranet - FlowerCore\n\n\n\n\n
\n \n

Blue Jay Lab Intranet

\n

BlueJay Network Infrastructure — 13 VLANs | 6 Nodes | Bare-Metal RKE2 | 5 WiFi SSIDs | 18 Domains | All Services Live

\n

Last updated: 2026-03-10

\n
REBUILD COMPLETE — All Services Live on Bare-Metal RKE2
\n
\n\n\n\n\n
\n

Overview

\n
\n
12
VLANs
\n
6
Physical Nodes
\n
10
ArgoCD Apps
\n
43+
DNS Entries
\n
17
Guacamole Conns
\n
5
WiFi SSIDs
\n
8
VPN Tunnels
\n
13
Public IPs
\n
18
Domains
\n
\n\n
Network Status: REBUILD COMPLETE. All 13 phases complete. Bare-metal RKE2 3-node cluster, 10 ArgoCD apps healthy, Cloudflare DNS on /28, 1Password Connect wired. All services live.
\n\n

Quick Links — Web UIs

\n
\n
pfSense
https://10.0.56.1
\n
UniFi Cloud Key
https://10.0.56.3
\n
Synology WiFi (SRM)
http://10.0.58.2:8000
\n
Traefik Dashboard
https://traefik.iamworkin.lan
\n
Cockpit (noc1)
https://10.0.56.10:9090
\n
Grafana
http://10.0.56.10:3000
\n
Prometheus
http://10.0.56.10:9091
\n
Guacamole
http://10.0.56.10:30080/guacamole/
\n
PKI Web
http://pki.iamworkin.lan:30081
\n
ArgoCD
https://argocd.iamworkin.lan
\n
Zabbix
https://zabbix.iamworkin.lan
\n
Gitea
https://gitea.iamworkin.lan
\n
Frontier Modem
http://192.168.254.254
\n
\n\n

Phase Progress

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PhaseDescriptionStatusProgress
1Frontier Modem ConfigDone100%
2pfSense Base (WAN, LAN, VIPs)Done100%
3VLAN Configuration (12 VLANs)Done100%
4Firewall Rules & AliasesDone100%
5Bare-Metal RKE2 ClusterDone100%
6OpenVPN (8 servers)Done100%
7NAT ConfigurationDone100%
8Traffic ShaperDone100%
9DNS + NTP + SNMPDone100%
10Switch + WiFi ConfigDone100%
11NOC1 + Bare-Metal RKE2Done100%
12GitOps + ArgoCDDone100%
13Documentation SyncDone100%
\n
\n\n\n
\n

ISP & WAN

\n
\n
\n
ISP: Frontier Communications
\n
    \n
  • Service: 1000/1000 Mbps fiber
  • \n
  • Account: 952-431-5646-020421-7
  • \n
  • Measured: 925 down / 677 up (MGMT VLAN)
  • \n
\n
\n
\n
Modem: NVG468MQ
\n
    \n
  • Web: http://192.168.254.254
  • \n
  • Credentials: admin / 5108967609
  • \n
  • Serial: 184795207512112
  • \n
  • Firmware: 9.3.0h7d91
  • \n
  • WAN IP: 74.32.187.152/22
  • \n
  • Config: DMZ to pfSense, WiFi OFF, firewall OFF
  • \n
\n
\n
\n

WAN Status

\n\n\n\n\n\n\n\n\n\n
PropertyValue
pfSense WAN Interfaceix3 (DHCP from modem)
pfSense WAN IP192.168.254.122 (double NAT intentional)
Public /28 Block74.40.140.16/28
Gateway74.40.140.30
Usable Range74.40.140.17 – 74.40.140.29 (13 IPs)
\n
ISP /28 Routing: LIVE. Public /28 (74.40.140.16/28) fully operational. 13 VIPs, 12 outbound NAT rules, 18 port forwards. Cloudflare DNS on /28 IPs.
\n

Modem Static Routes

\n\n\n\n\n\n\n
NameDestinationGatewayInterface
pfSense-Public-2874.40.140.16/28192.168.254.122LAN
pfSense-Private-Subnets10.0.0.0/8192.168.254.122LAN
\n

Public IP Allocation (13 usable)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
IPFull AddressAssignmentVLAN(s)Services
.1674.40.140.16Network addressUnusable
.1774.40.140.17 ANDREW + VPN60Andrew tenant primary + VPN :1194/:1195
.1874.40.140.18 MATT + VPN61Matt tenant primary + VPN :1194/:1195
.1974.40.140.19 DUSTIN + VPN62Dustin tenant primary + VPN :1194/:1195
.2074.40.140.20 ERIK + VPN63Erik tenant primary + VPN :1194/:1195
.2174.40.140.21 PROD57K8s Traefik shared ingress (flowercore.io)
.2274.40.140.22Reserved
.2374.40.140.23Reserved
.2474.40.140.24Reserved
.2574.40.140.25Reserved
.2674.40.140.26Reserved
.2774.40.140.27Reserved
.2874.40.140.28 SHARED59,64,65,66,67WORK+SCHOOL+GUEST+VOIP+EMPLOYEE outbound
.2974.40.140.29 HOME58Home traffic + Nintendo Switch static port NAT
.3074.40.140.30Gateway (Frontier)ISP\ + \ router
.3174.40.140.31BroadcastUnusable
\n
\n\n\n
\n

pfSense Firewall

\n
\n
\n
Netgate 4100
\n
    \n
  • Web: https://10.0.56.1
  • \n
  • Credentials: admin / SCOOBY_entry1latimer
  • \n
  • SSH: admin@10.0.56.1
  • \n
  • Hardware: 2x SFP+, 4x 2.5GbE (igc0-3)
  • \n
  • WAN: ix3 — LAN: igc0 (802.1Q trunk)
  • \n
  • Domain: iamworkin.lan
  • \n
\n
\n
\n
Firewall Stats
\n
    \n
  • Aliases: 36 (16 port, 5 host, 15 network)
  • \n
  • Rules: 90 active
  • \n
  • Policy: Air-gapped default — deny all, explicit allow
  • \n
  • SNMP: community bluejay_monitor
  • \n
  • SNMP Modules: mibII, netgraph, pf, hostres, bridge
  • \n
\n
\n
\n
Services
\n
    \n
  • DNS: Unbound (DNSSEC, WAN-only outgoing, prefetch)
  • \n
  • DHCP: dhcpd on all 12 VLAN interfaces (.100-.199)
  • \n
  • NTP: ntpd on all VLAN interfaces, DHCP option 42
  • \n
  • Traffic Shaper: 24 dummynet pipes, fq_codel
  • \n
\n
\n
\n

VLAN Configuration (12 VLANs)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
VLANNameSubnetDHCP RangeDown/Up (Mbps)PriorityPublic IP
56MGMT10.0.56.0/24.100-.199500 / 5005WAN DHCP
57PROD10.0.57.0/24.100-.199500 / 5005.21
58HOME10.0.58.0/24.100-.199800 / 8003.29
59EMPLOYEE10.0.59.0/24.100-.199500 / 5003.28 (shared)
60ANDREW10.0.60.0/24.100-.199300 / 3003.17
61MATT10.0.61.0/24.100-.199300 / 3003.18
62DUSTIN10.0.62.0/24.100-.199300 / 3003.19
63ERIK10.0.63.0/24.100-.199300 / 3003.20
64WORK10.0.64.0/24.100-.199500 / 5003.28 (shared)
65SCHOOL10.0.65.0/24.100-.199200 / 2001.28 (shared)
66GUEST10.0.66.0/24.100-.199100 / 501.28 (shared)
67VOIP10.0.67.0/24.100-.199100 / 1007.28 (shared)
\n
Firewall Policy: MGMT has full access. HOME/WORK/SCHOOL get general internet. GUEST isolated except PROD web. Tenants fully isolated from each other — only PROD, DNS, NAS, and internet. VOIP is SIP-only outbound.
\n
\n\n\n
\n

Switching & WiFi

\n
\n
\n
UniFi Switch USW-Lite-16-PoE
\n
    \n
  • IP: 10.0.56.2
  • \n
  • MAC: 74:ac:b9:e3:93:ba
  • \n
  • SSH: GOShDkH@10.0.56.2
  • \n
  • SSH Password: 6IDvT8vmQH6QqsP
  • \n
  • Firmware: 7.2.123.16565
  • \n
  • CLI: Type cli for Realtek switch CLI
  • \n
\n
\n
\n
UniFi Cloud Key G2
\n
    \n
  • Web: https://10.0.56.3
  • \n
  • Credentials: admin / nest7BEGGAR*revoked
  • \n
  • SSH: root@10.0.56.3 / 6IDvT8vmQH6QqsP
  • \n
  • Network Version: 7.1.61
  • \n
  • MongoDB: port 27117 (database ace)
  • \n
\n
\n
\n
Synology RT6600AX (AP Mode)
\n
    \n
  • Web: http://10.0.58.2:8000
  • \n
  • Credentials: bluejay / galileo_parisian8ADMIRE
  • \n
  • SSH: bluejay@10.0.58.2 port 22
  • \n
  • MAC: 90:09:d0:3d:64:ae
  • \n
  • Mode: AP (bridge), all trunk ports enabled
  • \n
\n
\n
\n

Switch Port Assignments

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
PortDeviceModeVLANStatus
1pfSense UplinkTrunk (All)56-67UP
2harvester3Trunk (All)56-67UP
3WiFi Uplink (Synology)Trunk, native 5857-67UP
4harvester2Trunk (All)56-67UP
5Cloud Key G2Access56 (MGMT)UP (PoE)
6harvester1Trunk (All)56-67UP
7AvailableDown
8noc1Trunk (All)56-67UP
9WorkstationAccess56 (MGMT)UP
10AvailableDown
11edge2 (Pi 4)Access57 (PROD)UP
12AvailableDown
13edge1 (Pi 5)Access57 (PROD)UP
14Synology NASAccess58 (HOME)UP
15AvailableDown
16Synology 2Access58 (HOME)UP
\n

WiFi SSIDs

\n\n\n\n\n\n\n\n\n\n
SSIDBridgeVLANTypePassword
BlueJay-Homebr0untagged (58)PrimaryStarling-Tundra-Condor-Coral
BlueJay-Employeebr259CustomMerlin~Ivory~Oakleaf~Bramble
BlueJay-Workbr364CustomForge.Hawk.Oakleaf.Topaz
BlueJay-Schoolbr465CustomHarbor-Eagle-Condor-Topaz
BlueJay-Guestgbr066Guest (isolation+NAT)Eagle.Oriole.Osprey.Silver
\n
\n\n\n
\n

DNS Directory

\n
All entries are pfSense Unbound host overrides under iamworkin.lan. 42+ total entries configured.
\n

Management Devices

\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
pfsense.iamworkin.lan10.0.56.1 pfSense firewall
switch.iamworkin.lan10.0.56.2 UniFi PoE Switch
unifi.iamworkin.lan10.0.56.3 UniFi Cloud Key G2
wifi.iamworkin.lan10.0.58.2 Synology WiFi Router (AP)
nas.iamworkin.lan10.0.58.3 Synology NAS
\n

Harvester Cluster

\n\n\n\n\n\n\n\n\n
HostnameIPRole
harvester.iamworkin.lan10.0.56.14 Harvester VIP (cluster dashboard)
harvester1.iamworkin.lan10.0.56.11 Harvester node 1
harvester2.iamworkin.lan10.0.56.12 Harvester node 2
harvester3.iamworkin.lan10.0.56.13 Harvester node 3
\n

NOC Services (noc1)

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
noc1.iamworkin.lan10.0.56.10 NOC management node (K3s)
acme.iamworkin.lan10.0.56.10step-ca ACME CA
pki.iamworkin.lan10.0.56.10PKI cert/CRL distribution
guac.iamworkin.lan10.0.56.10Apache Guacamole
grafana.iamworkin.lan10.0.56.10Grafana monitoring
prometheus.iamworkin.lan10.0.56.10Prometheus metrics
cockpit.iamworkin.lan10.0.56.10Cockpit web console
traefik.iamworkin.lan10.0.56.10Traefik dashboard (K3s)
gitea.iamworkin.lan10.0.56.10Gitea Git hosting (K3s, :3000/SSH :30022)
irc.iamworkin.lan10.0.56.10UnrealIRCd IRC server (K3s, TLS 6697)
intranet.iamworkin.lan10.0.56.10Lab intranet dashboard (K3s + Traefik TLS)
zabbix.iamworkin.lan10.0.56.10Zabbix monitoring (K3s, :30083, 10 hosts)
op-connect.iamworkin.lan10.0.56.101Password Connect (planned)
\n

RKE2 Workload Cluster

\n\n\n\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
rke2.iamworkin.lan10.0.56.118 RKE2 API server
rke2-node1.iamworkin.lan10.0.56.118RKE2 node 1 (on harvester2)
rke2-node2.iamworkin.lan10.0.56.119 RKE2 node 2 (on harvester1)
rke2-node3.iamworkin.lan10.0.56.120 RKE2 node 3 (on harvester3)
traefik.iamworkin.lan10.0.56.200 Traefik LoadBalancer (MetalLB)
rke2-ingress.iamworkin.lan10.0.56.200RKE2 ingress (MetalLB)
argocd.iamworkin.lan10.0.56.200ArgoCD GitOps (RKE2 via Traefik)
test.iamworkin.lan10.0.56.200RKE2 test workload
\n

Production / Edge Nodes

\n\n\n\n\n\n\n\n
HostnameIPRole
macmini.iamworkin.lan10.0.57.50 Mac Mini build node (Xcode)
edge1.iamworkin.lan10.0.57.15 Pi 5 + Hailo AI HAT+ 2
edge2.iamworkin.lan10.0.57.16 Pi 4 (Argon ONE, CI runner)
\n

Planned / Windows (pre-registered)

\n\n\n\n\n\n\n\n\n\n
HostnameIPRole
dc1.iamworkin.lan10.0.56.20 AD Domain Controller (planned)
wac1.iamworkin.lan10.0.56.21 Windows Admin Center (planned)
rds1.iamworkin.lan10.0.57.20 Remote Desktop Services (planned)
iis1.iamworkin.lan10.0.57.21 IIS Web Server (planned)
proxy.iamworkin.lan10.0.56.22 Squid Authenticated Proxy (planned)
\n
\n\n\n
\n

Kubernetes Clusters

\n

K3s (noc1 — Management Services)

\n
\n
\n
K3s on noc1
\n
    \n
  • Node: 10.0.56.10 (single-node)
  • \n
  • Version: K3s v1.34.5
  • \n
  • Traefik: Disabled (using NodePort)
  • \n
  • ServiceLB: Disabled
  • \n
  • Tools: kubectl v1.35.2, virtctl v1.7.0, helm v3.20.0
  • \n
\n
\n
\n
K3s Services
\n
    \n
  • Guacamole — :30080
  • \n
  • PKI Web — :30081
  • \n
  • Gitea — :3000 (SSH :30022)
  • \n
  • UnrealIRCd + Anope — :6667/:6697
  • \n
  • Zabbix — :30083
  • \n
\n
\n
\n

Harvester HCI (VM Platform)

\n
\n
\n
Harvester Cluster
\n
    \n
  • Dashboard: https://10.0.56.14
  • \n
  • Credentials: admin / getup-billion4AGAINST
  • \n
  • Version:\ + \ DECOMMISSIONED — Replaced by Bare-Metal RKE2
  • \n
  • Kubeconfig: /home/stoltz/.kube/rke2.yaml (WSL) on noc1
  • \n
  • Cluster Token: See 1Password
  • \n
\n
\n
\n
Harvester Nodes
\n
    \n
  • rke2-server: 10.0.56.11 — i7-1260P / 64GB
  • \n
  • rke2-agent1: 10.0.56.12 — i7-1260P / 64GB
  • \n
  • rke2-agent2: 10.0.56.13 — i5-1340P / 64GB (bare-metal openSUSE Leap 16)
  • \n
  • SSH: root@10.0.56.{11,12,13} (ed25519 key auth)
  • \n
  • Password: SSH Key Only — See 1Password
  • \n
\n
\n
\n
Harvester Resources
\n
    \n
  • VM Images: 11 (Ubuntu, openSUSE, Windows, SQL Server)
  • \n
  • VM Networks: 13 (12 VLAN bridges + mgmt-untagged)
  • \n
  • Storage: Longhorn (replica count 2)
  • \n
  • Active VMs: rke2-node1, rke2-node2, rke2-node3
  • \n
\n
\n
\n

RKE2 (Workload Cluster)

\n
\n
\n
RKE2 Cluster
\n
    \n
  • Version: RKE2 v1.34.5+rke2r1
  • \n
  • OS: openSUSE Leap 16.0 (bare-metal)
  • \n
  • CNI: Calico (VXLAN mode)
  • \n
  • Pod CIDR: 10.42.0.0/16
  • \n
  • Service CIDR: 10.43.0.0/16
  • \n
  • Kubeconfig: /root/.kube/rke2.yaml on noc1
  • \n
  • Token: bluejay-rke2-2026
  • \n
  • Root Password: BlueJay-RKE2-2026
  • \n
\n
\n
\n
RKE2 Nodes
\n
    \n
  • rke2-server: 10.0.56.11 (bare-metal)
  • \n
  • rke2-agent1: 10.0.56.12 (bare-metal)
  • \n
  • rke2-agent2: 10.0.56.13 (bare-metal)
  • \n
  • Specs: Full NUC hardware (bare-metal)
  • \n
  • SSH Key: ed25519 key (stoltz@IAMWORKIN-WS) (ed25519)
  • \n
\n
\n
\n
RKE2 Infrastructure
\n
    \n
  • MetalLB: v0.14.9, L2 mode, pool 10.0.56.200-220
  • \n
  • Traefik: v3.6.9, 2 replicas, LB 10.0.56.200
  • \n
  • Namespaces: fc-system, fc-tenant-andrew, fc-tenant-matt, fc-tenant-dustin, fc-tenant-erik, test, traefik-system, metallb-system
  • \n
  • NetworkPolicies: Applied to all 5 tenant namespaces
  • \n
  • Test: nginx + IngressRoute on test.iamworkin.lan verified
  • \n
\n
\n
\n

pfSense Static Routes (K8s)

\n\n\n\n\n\n\n
DestinationGatewayPurpose
10.42.0.0/1610.0.56.11 (rke2-server)Pod CIDR routing
10.43.0.0/1610.0.56.11 (rke2-server)Service CIDR routing
\n
\n\n\n
\n

NOC Services (noc1)

\n
\n
\n
noc1 Host
\n
    \n
  • IP: 10.0.56.10
  • \n
  • SSH: root@10.0.56.10
  • \n
  • Password: harbor-badge-kitten-valley-falcon
  • \n
  • OS: openSUSE Leap Micro 6.2 (immutable)
  • \n
  • CPU: Intel Celeron N5105 (4C/4T)
  • \n
  • RAM: 32 GB
  • \n
  • Disk: 1TB NVMe (929GB free)
  • \n
  • Runtimes: Podman 5.4.2, K3s v1.34.5
  • \n
\n
\n
\n

Service Directory

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ServiceURLPortCredentialsStatus
Cockpithttps://10.0.56.10:90909090root / harbor-badge-kitten-valley-falconOnline
Prometheushttp://10.0.56.10:90919091No auth (90-day retention)Online
Grafanahttp://10.0.56.10:30003000admin / holly-pine-atlas-crane Online
Node Exporterhttp://10.0.56.10:91009100Metrics onlyOnline
SNMP Exporterhttp://10.0.56.10:91169116pfSense SNMP scraperOnline
Guacamolehttp://10.0.56.10:30080/guacamole/30080guacadmin / fern-anchor-amber-viper Online
step-ca ACMEhttps://acme.iamworkin.lan:94439443Password: BlueJay-StepCA-2026 Online
PKI Webhttp://pki.iamworkin.lan:3008130081Public (CRL/certs)Online
Giteahttps://gitea.iamworkin.lan3000bluejay / maple-wren-slate-anvil Online
UnrealIRCdirc.iamworkin.lan:6697 (TLS)6697OPER: bluejay / NickServ: willow-heron-hawk-haven Online
Zabbixhttps://zabbix.iamworkin.lan30083Admin / fossil-ruby-kestrel-canyon Online
\n

RKE2 Services

\n\n\n\n\n\n\n

Prometheus Alerting (8 rules)

\n

NodeDown, PfSenseDown, HighCPU, HighMemory, DiskSpaceLow, and 3 additional rules. 2 Grafana dashboards: Node Exporter Full + BlueJay Network Overview.

\n

Guacamole Connection Groups (16 connections)

\n
ServiceURLPortCredentialsStatus
ArgoCDhttps://argocd.iamworkin.lan443 (via Traefik)admin / 6KJcJtH3SCAPrWVQ bluejay / 6KJcJtH3SCAPrWVQ Online
Traefikhttps://traefik.iamworkin.lan80/443 (MetalLB 10.0.56.200)admin / zenith-turret-falcon-umber (BasicAuth)Online
\n\n\n\n\n\n\n\n
GroupConnectionsProtocol
Kubernetes (6)rke2-server, rke2-agent1, rke2-agent2,\ + \ noc1SSH
Network Devices (4)pfSense, UniFi Cloud Key, UniFi Switch, Synology WiFiSSH
Servers (5)noc1, Mac Mini (SSH+VNC), Edge1 Pi5, Synology NAS, Edge2 Pi4SSH/VNC
Web Consoles (1)Traefik DashboardHTTPS
\n
\n\n\n
\n

VPN & Security

\n
OpenVPN Status: 8 servers were configured and verified but have been cleaned out pending ISP /28 fix. CA and certificates remain in pfSense config. Will re-create bound to new tenant VIPs (.17-.20) after Frontier restores /28 routing.
\n

OpenVPN Configuration

\n\n\n\n\n\n\n\n\n
TenantVIPTUN PortTAP PortTunnel (TUN)Tunnel (TAP)VLAN
ANDREW.171194/UDP1195/UDP10.0.68.0/2710.0.68.128/2760
MATT.181194/UDP1195/UDP10.0.68.32/2710.0.68.160/2761
DUSTIN.191194/UDP1195/UDP10.0.68.64/2710.0.68.192/2762
ERIK.201194/UDP1195/UDP10.0.68.96/2710.0.68.224/2763
\n

VPN Certificate Infrastructure

\n\n\n\n\n\n\n\n\n\n
ComponentDetails
CABlueJay VPN CA (4096-bit RSA, SHA-256, 10-year)
Server Certs8 (one per VPN instance, 2048-bit RSA)
Client Certs4 (one per tenant, 2048-bit RSA)
TLS AuthShared HMAC key across all servers
Data CiphersAES-256-GCM, AES-128-GCM, CHACHA20-POLY1305
\n

IPsec Site-to-Site (Planned)

\n\n\n\n\n\n\n
TunnelLocalRemotePhase 1Phase 2 SAs
Matt.29 (pfSense WAN)Matt's public IPIKEv2, AES-256-GCM, DH 14+MATT (10.0.61.0/24) + PROD (10.0.57.0/24)
Dustin.29 (pfSense WAN)Dustin's public IPIKEv2, AES-256-GCM, DH 14+DUSTIN (10.0.62.0/24) + PROD (10.0.57.0/24)
\n

PKI Hierarchy

\n\n\n\n\n\n\n\n\n\n
CAStatusPurpose
Root CA (IAmWorkin ACME CA)OperationalTrust anchor, ECDSA P-256, expires 2036
ACME CA (step-ca on noc1)OperationalAutomated cert issuance via ACME protocol
Network CAPlannedSwitch, AP, pfSense device certs
Windows AD CS CAPlannedDomain-joined machine/user certs
Internal Services CAPlannedK8s service mesh, inter-service mTLS
\n
\n\n\n
\n

Edge Nodes

\n
\n
\n
edge1 — Raspberry Pi 5 + Hailo AI
\n
    \n
  • IP: 10.0.57.15 (PROD VLAN 57)
  • \n
  • SSH: stoltz@10.0.57.15
  • \n
  • Password: lemon-torch-ruby-raven
  • \n
  • Hardware: Pi 5 16GB + Hailo-10H 40 TOPS
  • \n
  • OS: Debian 13 (trixie) aarch64
  • \n
  • PCIe: Gen 3 x1 (8.0 GT/s)
  • \n
  • Power: 27W USB-C
  • \n
  • .NET SDK: 10.0.103
  • \n
  • GitHub Runner: v2.332.0 (labels: pi5, hailo)
  • \n
  • Node Exporter: :9100
  • \n
  • Switch Port: 13
  • \n
\n
\n
\n
edge2 — Raspberry Pi 4 (Argon ONE)
\n
    \n
  • IP: 10.0.57.16 (PROD VLAN 57)
  • \n
  • SSH: stoltz@10.0.57.16
  • \n
  • Password: nebula-cipher-indigo-tango
  • \n
  • Hardware: Pi 4 Model B 4GB, Argon ONE case
  • \n
  • OS: Debian 13 (trixie) aarch64
  • \n
  • Fan Control: argononed.service
  • \n
  • .NET SDK: 10.0.103
  • \n
  • GitHub Runner: v2.332.0 (labels: pi4, ci-runner)
  • \n
  • Node Exporter: :9100
  • \n
  • Switch Port: 11
  • \n
\n
\n
\n
Mac Mini (Build/Test Node)
\n
    \n
  • IP: 10.0.57.50 (PROD VLAN 57)
  • \n
  • SSH: bluejay@10.0.57.50
  • \n
  • Password: indigene-new-neptune-nuthatch
  • \n
  • VNC Password: tacokisses
  • \n
  • Role: Xcode builds, automated browser/app testing
  • \n
\n
\n
\n
\n\n\n
\n

WiFi Networks

\n
Credentials: All WiFi passwords are stored in the IAmWorkin vault on 1Password. To connect a device, open the 1Password app, find the WiFi entry, and scan the QR code from there. Passwords are not stored in this page for security.
\n
QR Code Connection: Open 1Password → search for the SSID name → tap “Show QR Code” → scan with your device camera. The QR code encodes the full WIFI:T:WPA;S:{SSID};P:{PASSWORD};;; connection string.
\n\n
\n \n
\n
\n
BlueJay-Home
\n
HOME (VLAN 58)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-Home\n
\n
\n VLAN\n 58 (untagged on AP)\n
\n
\n Security\n WPA2/WPA3\n
\n
\n Password\n See 1Password\n
\n
\n Purpose\n Home network — personal / family use\n
\n
\n Bandwidth\n 800 / 800 Mbps\n
\n
\n Public IP\n 74.40.140.29\n
\n
\n
\n\n \n
\n
\n
BlueJay-Employee
\n
EMPLOYEE (VLAN 59)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-Employee\n
\n
\n VLAN\n 59\n
\n
\n Security\n WPA2/WPA3\n
\n
\n Password\n See 1Password\n
\n
\n Purpose\n Employee network — staff device access\n
\n
\n Bandwidth\n 500 / 500 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n\n \n
\n
\n
BlueJay-Work
\n
WORK (VLAN 64)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-Work\n
\n
\n VLAN\n 64\n
\n
\n Security\n WPA2/WPA3\n
\n
\n Password\n See 1Password\n
\n
\n Purpose\n Work network — business devices\n
\n
\n Bandwidth\n 500 / 500 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n\n \n
\n
\n
BlueJay-School
\n
SCHOOL (VLAN 65)
\n
\n
\n
\n \n \n \n \n \n Scan from 1Password app\n
\n
\n
\n
\n SSID\n BlueJay-School\n
\n
\n VLAN\n 65\n
\n
\n Security\n WPA2/WPA3\n
\n
\n Password\n See 1Password\n
\n
\n Purpose\n School network — student devices\n
\n
\n Bandwidth\n 200 / 200 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n\n \n
\n
\n
BlueJay-Guest
\n
GUEST (VLAN 66)
\n
\n
\n
\n \n \n \n \n \n Open network — no password required\n
\n
\n
\n
\n SSID\n BlueJay-Guest\n
\n
\n VLAN\n 66\n
\n
\n Security\n Open / Captive Portal\n
\n
\n Password\n None (open)\n
\n
\n Purpose\n Guest WiFi — fully isolated, NAT only\n
\n
\n Bandwidth\n 100 / 50 Mbps\n
\n
\n Public IP\n 74.40.140.28 (shared)\n
\n
\n
\n
\n\n

WiFi Access Point

\n
\n
\n
Synology RT6600AX (AP Mode)
\n
    \n
  • Management: http://10.0.58.2:8000
  • \n
  • Credentials: bluejay / galileo_parisian8ADMIRE
  • \n
  • Mode: Access Point (bridge mode), all trunk ports enabled
  • \n
  • Bands: Wi-Fi 6E (2.4 GHz + 5 GHz + 6 GHz)
  • \n
  • Switch Port: 3 (trunk, native VLAN 58)
  • \n
\n
\n
\n\n
Network Isolation: Each SSID maps to a separate VLAN with independent firewall rules and bandwidth limits. GUEST is fully isolated with NAT — no access to internal resources. EMPLOYEE, WORK, and SCHOOL share public IP .28 with traffic shaping.
\n
\n\n\n
\n

Credentials & 1Password

\n
\n
\n
1Password Connect Server
\n
    \n
  • API: http://10.0.56.10:8180
  • \n
  • Sync: http://10.0.56.10:8181
  • \n
  • Host: noc1 (Podman containers)
  • \n
  • Status: Online
  • \n
\n
\n
\n
1Password K8s Operator
\n
    \n
  • Namespace: onepassword-system
  • \n
  • Chart: 1password/connect v2.3.0
  • \n
  • Operator: v1.11.0
  • \n
  • Poll Interval: 600s
  • \n
  • Status: Online
  • \n
\n
\n
\n
IAmWorkin Vault
\n
    \n
  • Vault Name: IAmWorkin
  • \n
  • Items: 26+ credentials
  • \n
  • Rotation: Quarterly (Jan/Apr/Jul/Oct)
  • \n
  • Script: /opt/scripts/rotate-credentials.sh
  • \n
  • Timer: credential-rotation.timer
  • \n
\n
\n
\n\n
All infrastructure credentials are managed in 1Password. The IAmWorkin vault contains credentials for every service listed on this intranet. K8s workloads (Zabbix, Matrix, Guacamole, Mail, IRC, Gitea, ArgoCD) sync secrets automatically via OnePasswordItem CRDs. Credential rotation runs quarterly via systemd timer.
\n\n

K8s Secret Sync (OnePasswordItem CRDs)

\n\n\n\n\n\n\n\n\n\n\n\n
NamespaceSecret NameSource (1Password Item)Status
zabbixzabbix-credentialsZabbix MonitoringSynced
matrixmatrix-credentialsMatrix SynapseSynced
guacamoleguacamole-credentialsApache GuacamoleSynced
mailmail-credentialsMail ServerSynced
ircirc-credentialsIRC ServicesSynced
giteagitea-credentialsGiteaSynced
argocdargocd-credentialsArgoCDSynced
\n\n

Credential Rotation

\n
\n
\n
Rotation Script
\n
    \n
  • Path: /opt/scripts/rotate-credentials.sh
  • \n
  • Usage: rotate-credentials.sh {service|all} [--dry-run]
  • \n
  • Services: grafana, guacamole, zabbix, argocd, gitea, snappymail, traefik, matrix, harvester
  • \n
  • Schedule: Quarterly (1st of Jan/Apr/Jul/Oct at 03:00 UTC)
  • \n
  • Log: /var/log/credential-rotation.log
  • \n
\n
\n
\n
\n\n\n
\n

Planned Services

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ServiceIPHostRoleStatus
Windows DC110.0.56.20Harvester VMAD Domain Controller (iamworkin.lan)Planned
Windows WAC110.0.56.21Harvester VMWindows Admin CenterPlanned
Windows RDS110.0.57.20Harvester VMRemote Desktop ServicesPlanned
Windows IIS110.0.57.21Harvester VMIIS Web ServerPlanned
Gitea10.0.56.10K3s (noc1)Git hosting (gitea.iamworkin.lan)Live
UnrealIRCd10.0.56.10K3s (noc1)IRC server (irc.iamworkin.lan:6697)Live
Zabbix10.0.56.10K3s (noc1)Network monitoring (zabbix.iamworkin.lan, 10 hosts)Live
1Password Connect10.0.56.10noc1Secrets management APIPlanned
Squid Proxy10.0.56.22Harvester VMAuthenticated web proxy (Kerberos/LDAP)Planned
ArgoCD10.0.56.200RKE2 (via Traefik)GitOps for K8s workloads (argocd.iamworkin.lan)Live
\n
\n\n\n
\n

Network Topology

\n
\n\n
Internet
\n
\n
Frontier ONT + NVG468MQ Modem

WAN: 74.32.187.152/28: .17-.29

192.168.254.254 • DMZ to pfSense

\n
\n
pfSense Netgate 4100

WAN: ix3 (.122)LAN: igc0 (802.1Q trunk)

12 VLANs • 36 aliases • 90 rules • DNS/DHCP/NTP/SNMP

\n
\n
UniFi USW-Lite-16-PoE Switch

10.0.56.2 • 16 ports • VLANs 56-67

\n
\n\n
\n
\n
noc1
\n

10.0.56.10 MGMT

\n

Celeron N5105 • 32GB • K3s + Podman

\n
    \n
  • Guacamole :30080
  • Grafana :3000
  • Prometheus :9091
  • \n
  • step-ca :9443
  • Gitea :3000
  • IRC :6697
  • \n
  • Zabbix :30083
  • Cockpit\ + \ :9090
  • Puppet :8140
  • \n
\n
\n
\n
Harvester Cluster
\n

VIP: 10.0.56.14 MGMT

\n
    \n
  • harvester1: .11 (i7-1260P/64GB)
  • \n
  • harvester2: .12 (i7-1260P/64GB)
  • \n
  • harvester3: .13 (i5-1340P/64GB)
  • \n
\n

Harvester v1.7.1 • Longhorn • Rancher embedded

\n
\n
\n
RKE2 Workload Cluster
\n

Traefik: 10.0.56.200 MGMT

\n
    \n
  • rke2-node1: .118 (on harvester2)
  • \n
  • rke2-node2: .119 (on harvester1)
  • \n
  • rke2-node3: .120 (on harvester3)
  • \n
\n

Calico • MetalLB • Traefik v3.6.10 • ArgoCD

\n
\n
\n
WiFi (Synology RT6600AX)
\n

10.0.58.2 HOME

\n
    \n
  • BlueJay-Home (untagged)
  • BlueJay-Employee (VLAN 59)
  • \n
  • BlueJay-Work (VLAN 64)
  • BlueJay-School (VLAN 65)
  • \n
  • BlueJay-Guest (VLAN 66)
  • \n
\n
\n
\n
PROD Nodes
\n

PROD VLAN 57

\n
    \n
  • Mac Mini: 10.0.57.50 (Xcode)
  • \n
  • edge1 Pi5: 10.0.57.15 (Hailo AI)
  • \n
  • edge2 Pi4: 10.0.57.16 (CI runner)
  • \n
\n
\n
\n
Network Devices
\n
    \n
  • Cloud Key: 10.0.56.3
  • \n
  • NAS: 10.0.58.3
  • \n
  • Modem: 192.168.254.254
  • \n
\n
\n
\n
\n
\n\n\n
\n

Domains

\n
\n
17
Registered Domains
\n
1
Internal Domain
\n
1
Blog Hosting (DreamHost)
\n
18
Total Domains
\n
\n\n

FlowerCore Domains

\n\n\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
flowercore.ioFlowerCoreAndrewProduction APICloudflareNamecheap
flowerinsider.xyzFlowerCoreAndrewDev/stagingNamecheapNamecheap
flowerinsider.comFlowerCore CoAndrewCompany siteNamecheapNamecheap
flowerinsider.nlFlowerCore CoAndrewDutch siteNamecheapNamecheap
\n\n

Work Domains

\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
iamwork.inWorkAndrewEmployee portal, IVRNamecheapNamecheap
iamworkin.comWorkAndrewRedirectNamecheapNamecheap
\n\n

Personal & Tenant Domains

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
ackeroni.comErikErikPersonalNamecheapNamecheap
erckak.comErikErikPersonalNamecheapNamecheap
erckak.devErikErikDeveloper portfolioNamecheapNamecheap
digirido.comRandomAndrewDigiKey testingNamecheapNamecheap
timeforta.coDustinDustinPersonalNamecheapNamecheap
shenanjia.comWifeWifePersonal siteNamecheapNamecheap
bluejay.apiPersonal FunAndrewAPI experimentsNamecheapNamecheap
bluejay.devPersonal FunAndrewDev projectsNamecheapNamecheap
jayblue.devPersonal FunAndrewDev projectsNamecheapNamecheap
z.orbRandomAndrewShort URLNamecheapNamecheap
\n\n

Blog & Content Domains

\n\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderRegistrar
pebbleandpeanut.comBlogAndrewPersonal blogDreamHostNamecheap
pebblesandpeanuts.comBlogAndrewAlt redirectNamecheapNamecheap
\n\n

Internal Domain

\n\n\n\n\n\n
DomainCategoryOwnerPurposeDNS ProviderNotes
iamworkin.lanInternalAndrewInternal infrastructure, future AD DSpfSense Unbound43+ host overrides, not publicly registered
\n\n

Namecheap API

\n
\n
\n
API Configuration
\n
    \n
  • Base URL: https://api.namecheap.com/xml.response
  • \n
  • API User: astoltz
  • \n
  • API Key: e36f347844fb4cc3a82d4e0f4e4af82e
  • \n
  • Sandbox URL: https://api.sandbox.namecheap.com/xml.response
  • \n
\n
\n
\n
Dynamic DNS
\n
    \n
  • Hostname: gateway.iamwork.in
  • \n
  • Points to: pfSense WAN IP (auto-updated)
  • \n
  • DDNS Endpoint: https://dynamicdns.park-your-domain.com/update?host=gateway&domain=iamwork.in&password=d8ad7194c9224c26bc50cfa5feb8764e
  • \n
  • Update Method: pfSense Dynamic DNS client or cron
  • \n
\n
\n
\n\n

Internal DNS Architecture

\n
Split-Horizon DNS (planned): External requests to flowercore.io resolve via Cloudflare to public IP .21. Internal requests resolve via pfSense Unbound to K8s MetalLB VIP (10.0.56.200), avoiding NAT hairpin. All internal infrastructure uses iamworkin.lan zone.
\n\n

Planned IPv6 (ULA)

\n\n\n\n\n\n
PrefixSchemeMethod
fdbc:56:XX::/64XX = VLAN ID (e.g., fdbc:56:56::/64 for MGMT)SLAAC + DHCPv6 (servers),\ + \ SLAAC-only (clients)
\n
\n\n\n\n\n" +kind: ConfigMap +metadata: + name: intranet-html + namespace: intranet +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: intranet + name: intranet + namespace: intranet +spec: + replicas: 1 + selector: + matchLabels: + app: intranet + template: + metadata: + labels: + app: intranet + spec: + containers: + - image: nginx:alpine + livenessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 5 + periodSeconds: 10 + name: nginx + ports: + - containerPort: 80 + name: http + readinessProbe: + httpGet: + path: /healthz + port: 80 + initialDelaySeconds: 3 + periodSeconds: 5 + resources: + limits: + cpu: 50m + memory: 64Mi + requests: + cpu: 5m + memory: 16Mi + volumeMounts: + - mountPath: /etc/nginx/conf.d/default.conf + name: nginx-conf + subPath: default.conf + - mountPath: /usr/share/nginx/html + name: html + volumes: + - configMap: + name: intranet-nginx-conf + name: nginx-conf + - configMap: + name: intranet-html + name: html +--- +apiVersion: v1 +kind: Service +metadata: + name: intranet + namespace: intranet +spec: + ports: + - name: http + port: 80 + targetPort: 80 + selector: + app: intranet +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: intranet-tls + namespace: intranet +spec: + dnsNames: + - intranet.iamworkin.lan + issuerRef: + kind: ClusterIssuer + name: step-ca-acme + secretName: intranet-tls +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: intranet + namespace: intranet +spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`intranet.iamworkin.lan`) + services: + - name: intranet + port: 80 + tls: + secretName: intranet-tls diff --git a/apps/irc/irc.yaml b/apps/irc/irc.yaml index d85873e..9d8fb69 100644 --- a/apps/irc/irc.yaml +++ b/apps/irc/irc.yaml @@ -1,681 +1,681 @@ -# UnrealIRCd + Anope IRC Services -# ArgoCD managed - BlueJay Lab -# Credentials: 1Password → OnePasswordItem → K8s Secret → initContainer sed injection ---- -apiVersion: v1 -kind: Namespace -metadata: - name: irc - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# 1Password → K8s Secret sync -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: irc-credentials - namespace: irc -spec: - itemPath: "vaults/IAmWorkin/items/IRC UnrealIRCd" ---- -# TLS Certificate for IRC -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: irc-tls - namespace: irc -spec: - secretName: irc-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - irc.iamworkin.lan ---- -# UnrealIRCd configuration template (passwords replaced by placeholders) -apiVersion: v1 -kind: ConfigMap -metadata: - name: unrealircd-config-template - namespace: irc -data: - unrealircd.conf: | - /* BlueJay Lab IRC - UnrealIRCd 6.x config */ - /* Managed by ArgoCD */ - /* Credentials injected from 1Password at pod startup */ - - include "modules.default.conf"; - include "help/help.conf"; - include "operclass.default.conf"; - include "snomasks.default.conf"; - - loadmodule "cloak_sha256"; - - me { - name "irc.iamworkin.lan"; - info "BlueJay Lab IRC Server"; - sid 001; - } - - admin { - "BlueJay Lab IRC"; - "admin@iamwork.in"; - } - - class clients { - pingfreq 90; - maxclients 500; - sendq 200k; - recvq 8000; - } - - class opers { - pingfreq 90; - maxclients 50; - sendq 1M; - recvq 8000; - } - - class servers { - pingfreq 60; - connfreq 15; - maxclients 10; - sendq 20M; - } - - allow { - mask *; - class clients; - maxperip 5; - } - - listen { - ip *; - port 6667; - } - - listen { - ip *; - port 6697; - options { tls; } - tls-options { - certificate "/app/conf/tls/server.cert.pem"; - key "/app/conf/tls/server.key.pem"; - } - } - - listen { - ip *; - port 8067; - } - - oper bluejay { - mask *; - password "__OPER_PASSWORD__"; - operclass netadmin-with-override; - class opers; - } - - drpass { - restart "__OPER_PASSWORD__"; - die "__OPER_PASSWORD__"; - } - - link services.iamworkin.lan { - incoming { - mask *; - } - password "__LINK_PASSWORD__"; - class servers; - } - - ulines { - services.iamworkin.lan; - } - - log { - source { - all; - \\!debug; - } - destination { - channel "#ops"; - } - } - - set { - network-name "BlueJayIRC"; - default-server "irc.iamworkin.lan"; - services-server "services.iamworkin.lan"; - stats-server "stats.iamworkin.lan"; - help-channel "#general"; - cloak-keys { - "ZWKeb8YevNiL45Xdh2p5u4tv2xksWgb8YPQSvmerBmNObyGbTDGnU4PNomZaLbZ1D9M2Cy6njM1XLJUkJhAx1oY3coBdZoPykEo7"; - "KqRaLeA6ijOnWDdCqYtJ6rb1VgR8lYnU9Sey7cbRhi3PsGzD5gZONJXyUdbJ7bD26QKCuiDydBsccUVKC3lYN0HJ9sGTlOYR3c2m"; - "2I4oopLDY79Fr4Mucy63EVOfkelVV23nESPWoqMnP1pUc8Yg0D4RK1mVtxyEhdTPpLFyKgG4fRlb6R33eHoQe7yi7moOu4W1Waw6"; - } - kline-address "admin@iamwork.in"; - maxchannelsperuser 25; - anti-flood { - everyone { - connect-flood 3:60; - } - } - options { - hide-ulines; - show-connect-info; - } - - /* TLS config */ - tls { - certificate "/app/conf/tls/server.cert.pem"; - key "/app/conf/tls/server.key.pem"; - trusted-ca-file "/etc/ssl/certs/ca-certificates.crt"; - } - - /* Allow plaintext for server-to-server links (Anope is internal) */ - plaintext-policy { - server allow; - } - } ---- -# Anope configuration template (passwords replaced by placeholders) -apiVersion: v1 -kind: ConfigMap -metadata: - name: anope-config-template - namespace: irc -data: - services.conf: | - define - { - name = "services.host" - value = "services.iamworkin.lan" - } - - uplink - { - host = "unrealircd.irc.svc.cluster.local" - port = 8067 - password = "__LINK_PASSWORD__" - } - - serverinfo - { - name = "services.iamworkin.lan" - description = "BlueJay IRC Services" - pid = "/anope/data/services.pid" - motd = "/anope/data/services.motd" - } - - module { name = "unreal4" } - - networkinfo - { - networkname = "BlueJayIRC" - nicklen = 31 - userlen = 10 - hostlen = 64 - chanlen = 32 - } - - options - { - casemap = "ascii" - strictpasswords = yes - readtimeout = 5s - warningtimeout = 4h - } - - module { name = "enc_sha256" } - - /* Service pseudo-client definitions */ - service - { - nick = "NickServ" - user = "services" - host = "services.host" - gecos = "Nickname Registration Service" - } - - service - { - nick = "ChanServ" - user = "services" - host = "services.host" - gecos = "Channel Registration Service" - } - - service - { - nick = "OperServ" - user = "services" - host = "services.host" - gecos = "Operator Service" - } - - service - { - nick = "BotServ" - user = "services" - host = "services.host" - gecos = "Bot Service" - } - - service - { - nick = "HostServ" - user = "services" - host = "services.host" - gecos = "vHost Service" - } - - service - { - nick = "MemoServ" - user = "services" - host = "services.host" - gecos = "Memo Service" - } - - service - { - nick = "Global" - user = "services" - host = "services.host" - gecos = "Global Noticer" - } - - /* Module configurations */ - module - { - name = "nickserv" - client = "NickServ" - defaults = "kill_quick ns_secure ns_private hide_email" - registration = "none" - expire = 90d - } - - module { name = "ns_identify" } - module { name = "ns_register" } - module { name = "ns_set" } - module { name = "ns_drop" } - module { name = "ns_recover" } - module { name = "ns_info" } - module { name = "ns_list" } - module { name = "ns_access" } - module { name = "ns_group" } - - module - { - name = "chanserv" - client = "ChanServ" - defaults = "keeptopic peace cs_secure" - expire = 14d - } - - module { name = "cs_register" } - module { name = "cs_set" } - module { name = "cs_access" } - module { name = "cs_ban" } - module { name = "cs_kick" } - module { name = "cs_mode" } - module { name = "cs_topic" } - module { name = "cs_info" } - module { name = "cs_list" } - module { name = "cs_drop" } - - module - { - name = "operserv" - client = "OperServ" - } - - module { name = "os_akill" } - module { name = "os_mode" } - module { name = "os_kick" } - module { name = "os_kill" } - module { name = "os_list" } - module { name = "os_stats" } - module { name = "os_reload" } - module { name = "os_shutdown" } - - module - { - name = "botserv" - client = "BotServ" - defaults = "dontkickops fantasy greet" - } - - module { name = "bs_bot" } - module { name = "bs_assign" } - - module - { - name = "hostserv" - client = "HostServ" - } - - module { name = "hs_set" } - module { name = "hs_request" } - - module - { - name = "memoserv" - client = "MemoServ" - maxmemos = 20 - } - - module { name = "ms_send" } - module { name = "ms_read" } - module { name = "ms_del" } - module { name = "ms_list" } - - module - { - name = "global" - client = "Global" - } - - module { name = "gl_global" } - - opertype - { - name = "Services Root" - commands = "*" - privs = "*" - } - - oper - { - name = "bluejay" - type = "Services Root" - } - - module - { - name = "db_flatfile" - database = "anope.db" - fork = no - } - - log - { - target = "services.log" - admin = "*" - override = "chanserv/* nickserv/* operserv/*" - commands = "chanserv/* nickserv/* operserv/*" - servers = "*" - channels = "*" - users = "connect disconnect" - } ---- -# UnrealIRCd PVC -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: unrealircd-data - namespace: irc -spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 1Gi ---- -# Anope PVC -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: anope-data - namespace: irc -spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 1Gi ---- -# UnrealIRCd Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: unrealircd - namespace: irc - labels: - app: unrealircd -spec: - replicas: 1 - selector: - matchLabels: - app: unrealircd - template: - metadata: - labels: - app: unrealircd - spec: - initContainers: - - name: inject-credentials - image: busybox:1.36 - command: ["sh", "-c"] - args: - - | - OPER_PW=$(cat /secrets/password) - LINK_PW=$(cat /secrets/Link-Password) - sed -e "s|__OPER_PASSWORD__|${OPER_PW}|g" \ - -e "s|__LINK_PASSWORD__|${LINK_PW}|g" \ - /config-template/unrealircd.conf > /injected-config/unrealircd.conf - echo "Credentials injected into unrealircd.conf" - volumeMounts: - - name: irc-credentials - mountPath: /secrets - readOnly: true - - name: unrealircd-config-template - mountPath: /config-template - readOnly: true - - name: injected-config - mountPath: /injected-config - - name: copy-tls - image: busybox:1.36 - command: ["sh", "-c"] - args: - - | - cp /tls-secret/tls.crt /tls/server.cert.pem - cp /tls-secret/tls.key /tls/server.key.pem - chmod 644 /tls/server.cert.pem - chmod 644 /tls/server.key.pem - chown 1000:1000 /tls/server.cert.pem /tls/server.key.pem 2>/dev/null || true - chmod 777 /data - volumeMounts: - - name: irc-tls-secret - mountPath: /tls-secret - readOnly: true - - name: irc-tls - mountPath: /tls - - name: unrealircd-data - mountPath: /data - containers: - - name: unrealircd - image: djlegolas/unrealircd:6.1.9.1 - ports: - - containerPort: 6667 - name: irc-plain - - containerPort: 6697 - name: irc-tls - - containerPort: 8067 - name: services-link - volumeMounts: - - name: injected-config - mountPath: /app/conf/unrealircd.conf - subPath: unrealircd.conf - - name: unrealircd-data - mountPath: /app/data - - name: irc-tls - mountPath: /app/conf/tls - resources: - requests: - memory: 64Mi - cpu: 50m - limits: - memory: 256Mi - cpu: 250m - volumes: - - name: irc-credentials - secret: - secretName: irc-credentials - - name: unrealircd-config-template - configMap: - name: unrealircd-config-template - - name: injected-config - emptyDir: {} - - name: unrealircd-data - persistentVolumeClaim: - claimName: unrealircd-data - - name: irc-tls-secret - secret: - secretName: irc-tls - - name: irc-tls - emptyDir: {} ---- -# Anope IRC Services Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: anope - namespace: irc - labels: - app: anope -spec: - replicas: 1 - selector: - matchLabels: - app: anope - template: - metadata: - labels: - app: anope - spec: - initContainers: - - name: inject-credentials - image: busybox:1.36 - command: ["sh", "-c"] - args: - - | - LINK_PW=$(cat /secrets/Link-Password) - sed -e "s|__LINK_PASSWORD__|${LINK_PW}|g" \ - /config-template/services.conf > /injected-config/services.conf - echo "Credentials injected into services.conf" - volumeMounts: - - name: irc-credentials - mountPath: /secrets - readOnly: true - - name: anope-config-template - mountPath: /config-template - readOnly: true - - name: injected-config - mountPath: /injected-config - - name: fix-perms - image: busybox:1.36 - command: ["sh", "-c"] - args: - - | - mkdir -p /data/db /data/logs /data/runtime - touch /data/anope.db /data/services.motd - chmod 666 /data/anope.db - chown -R 10000:10000 /data 2>/dev/null || chmod -R 777 /data - echo "Anope data dir prepared: $(ls -la /data/anope.db)" - volumeMounts: - - name: anope-data - mountPath: /data - containers: - - name: anope - image: anope/anope:latest - volumeMounts: - - name: injected-config - mountPath: /anope/conf/services.conf - subPath: services.conf - - name: anope-data - mountPath: /anope/data - resources: - requests: - memory: 64Mi - cpu: 25m - limits: - memory: 128Mi - cpu: 100m - volumes: - - name: irc-credentials - secret: - secretName: irc-credentials - - name: anope-config-template - configMap: - name: anope-config-template - - name: injected-config - emptyDir: {} - - name: anope-data - persistentVolumeClaim: - claimName: anope-data ---- -# UnrealIRCd Service -apiVersion: v1 -kind: Service -metadata: - name: unrealircd - namespace: irc -spec: - selector: - app: unrealircd - ports: - - port: 6667 - targetPort: 6667 - name: irc-plain - - port: 6697 - targetPort: 6697 - name: irc-tls - - port: 8067 - targetPort: 8067 - name: services-link ---- -# Anope Service -apiVersion: v1 -kind: Service -metadata: - name: anope - namespace: irc -spec: - selector: - app: anope - ports: - - port: 8067 - targetPort: 8067 - name: services-link ---- -# Traefik IngressRouteTCP - IRC plain (6667) -apiVersion: traefik.io/v1alpha1 -kind: IngressRouteTCP -metadata: - name: irc-plain - namespace: irc -spec: - entryPoints: - - irc - routes: - - match: HostSNI(`*`) - services: - - name: unrealircd - port: 6667 ---- -# Traefik IngressRouteTCP - IRC TLS passthrough (6697) -apiVersion: traefik.io/v1alpha1 -kind: IngressRouteTCP -metadata: - name: irc-tls - namespace: irc -spec: - entryPoints: - - irctls - routes: - - match: HostSNI(`*`) - services: - - name: unrealircd - port: 6697 - tls: - passthrough: true +# UnrealIRCd + Anope IRC Services +# ArgoCD managed - BlueJay Lab +# Credentials: 1Password → OnePasswordItem → K8s Secret → initContainer sed injection +--- +apiVersion: v1 +kind: Namespace +metadata: + name: irc + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# 1Password → K8s Secret sync +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: irc-credentials + namespace: irc +spec: + itemPath: "vaults/IAmWorkin/items/IRC UnrealIRCd" +--- +# TLS Certificate for IRC +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: irc-tls + namespace: irc +spec: + secretName: irc-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - irc.iamworkin.lan +--- +# UnrealIRCd configuration template (passwords replaced by placeholders) +apiVersion: v1 +kind: ConfigMap +metadata: + name: unrealircd-config-template + namespace: irc +data: + unrealircd.conf: | + /* BlueJay Lab IRC - UnrealIRCd 6.x config */ + /* Managed by ArgoCD */ + /* Credentials injected from 1Password at pod startup */ + + include "modules.default.conf"; + include "help/help.conf"; + include "operclass.default.conf"; + include "snomasks.default.conf"; + + loadmodule "cloak_sha256"; + + me { + name "irc.iamworkin.lan"; + info "BlueJay Lab IRC Server"; + sid 001; + } + + admin { + "BlueJay Lab IRC"; + "admin@iamwork.in"; + } + + class clients { + pingfreq 90; + maxclients 500; + sendq 200k; + recvq 8000; + } + + class opers { + pingfreq 90; + maxclients 50; + sendq 1M; + recvq 8000; + } + + class servers { + pingfreq 60; + connfreq 15; + maxclients 10; + sendq 20M; + } + + allow { + mask *; + class clients; + maxperip 5; + } + + listen { + ip *; + port 6667; + } + + listen { + ip *; + port 6697; + options { tls; } + tls-options { + certificate "/app/conf/tls/server.cert.pem"; + key "/app/conf/tls/server.key.pem"; + } + } + + listen { + ip *; + port 8067; + } + + oper bluejay { + mask *; + password "__OPER_PASSWORD__"; + operclass netadmin-with-override; + class opers; + } + + drpass { + restart "__OPER_PASSWORD__"; + die "__OPER_PASSWORD__"; + } + + link services.iamworkin.lan { + incoming { + mask *; + } + password "__LINK_PASSWORD__"; + class servers; + } + + ulines { + services.iamworkin.lan; + } + + log { + source { + all; + \\!debug; + } + destination { + channel "#ops"; + } + } + + set { + network-name "BlueJayIRC"; + default-server "irc.iamworkin.lan"; + services-server "services.iamworkin.lan"; + stats-server "stats.iamworkin.lan"; + help-channel "#general"; + cloak-keys { + "ZWKeb8YevNiL45Xdh2p5u4tv2xksWgb8YPQSvmerBmNObyGbTDGnU4PNomZaLbZ1D9M2Cy6njM1XLJUkJhAx1oY3coBdZoPykEo7"; + "KqRaLeA6ijOnWDdCqYtJ6rb1VgR8lYnU9Sey7cbRhi3PsGzD5gZONJXyUdbJ7bD26QKCuiDydBsccUVKC3lYN0HJ9sGTlOYR3c2m"; + "2I4oopLDY79Fr4Mucy63EVOfkelVV23nESPWoqMnP1pUc8Yg0D4RK1mVtxyEhdTPpLFyKgG4fRlb6R33eHoQe7yi7moOu4W1Waw6"; + } + kline-address "admin@iamwork.in"; + maxchannelsperuser 25; + anti-flood { + everyone { + connect-flood 3:60; + } + } + options { + hide-ulines; + show-connect-info; + } + + /* TLS config */ + tls { + certificate "/app/conf/tls/server.cert.pem"; + key "/app/conf/tls/server.key.pem"; + trusted-ca-file "/etc/ssl/certs/ca-certificates.crt"; + } + + /* Allow plaintext for server-to-server links (Anope is internal) */ + plaintext-policy { + server allow; + } + } +--- +# Anope configuration template (passwords replaced by placeholders) +apiVersion: v1 +kind: ConfigMap +metadata: + name: anope-config-template + namespace: irc +data: + services.conf: | + define + { + name = "services.host" + value = "services.iamworkin.lan" + } + + uplink + { + host = "unrealircd.irc.svc.cluster.local" + port = 8067 + password = "__LINK_PASSWORD__" + } + + serverinfo + { + name = "services.iamworkin.lan" + description = "BlueJay IRC Services" + pid = "/anope/data/services.pid" + motd = "/anope/data/services.motd" + } + + module { name = "unreal4" } + + networkinfo + { + networkname = "BlueJayIRC" + nicklen = 31 + userlen = 10 + hostlen = 64 + chanlen = 32 + } + + options + { + casemap = "ascii" + strictpasswords = yes + readtimeout = 5s + warningtimeout = 4h + } + + module { name = "enc_sha256" } + + /* Service pseudo-client definitions */ + service + { + nick = "NickServ" + user = "services" + host = "services.host" + gecos = "Nickname Registration Service" + } + + service + { + nick = "ChanServ" + user = "services" + host = "services.host" + gecos = "Channel Registration Service" + } + + service + { + nick = "OperServ" + user = "services" + host = "services.host" + gecos = "Operator Service" + } + + service + { + nick = "BotServ" + user = "services" + host = "services.host" + gecos = "Bot Service" + } + + service + { + nick = "HostServ" + user = "services" + host = "services.host" + gecos = "vHost Service" + } + + service + { + nick = "MemoServ" + user = "services" + host = "services.host" + gecos = "Memo Service" + } + + service + { + nick = "Global" + user = "services" + host = "services.host" + gecos = "Global Noticer" + } + + /* Module configurations */ + module + { + name = "nickserv" + client = "NickServ" + defaults = "kill_quick ns_secure ns_private hide_email" + registration = "none" + expire = 90d + } + + module { name = "ns_identify" } + module { name = "ns_register" } + module { name = "ns_set" } + module { name = "ns_drop" } + module { name = "ns_recover" } + module { name = "ns_info" } + module { name = "ns_list" } + module { name = "ns_access" } + module { name = "ns_group" } + + module + { + name = "chanserv" + client = "ChanServ" + defaults = "keeptopic peace cs_secure" + expire = 14d + } + + module { name = "cs_register" } + module { name = "cs_set" } + module { name = "cs_access" } + module { name = "cs_ban" } + module { name = "cs_kick" } + module { name = "cs_mode" } + module { name = "cs_topic" } + module { name = "cs_info" } + module { name = "cs_list" } + module { name = "cs_drop" } + + module + { + name = "operserv" + client = "OperServ" + } + + module { name = "os_akill" } + module { name = "os_mode" } + module { name = "os_kick" } + module { name = "os_kill" } + module { name = "os_list" } + module { name = "os_stats" } + module { name = "os_reload" } + module { name = "os_shutdown" } + + module + { + name = "botserv" + client = "BotServ" + defaults = "dontkickops fantasy greet" + } + + module { name = "bs_bot" } + module { name = "bs_assign" } + + module + { + name = "hostserv" + client = "HostServ" + } + + module { name = "hs_set" } + module { name = "hs_request" } + + module + { + name = "memoserv" + client = "MemoServ" + maxmemos = 20 + } + + module { name = "ms_send" } + module { name = "ms_read" } + module { name = "ms_del" } + module { name = "ms_list" } + + module + { + name = "global" + client = "Global" + } + + module { name = "gl_global" } + + opertype + { + name = "Services Root" + commands = "*" + privs = "*" + } + + oper + { + name = "bluejay" + type = "Services Root" + } + + module + { + name = "db_flatfile" + database = "anope.db" + fork = no + } + + log + { + target = "services.log" + admin = "*" + override = "chanserv/* nickserv/* operserv/*" + commands = "chanserv/* nickserv/* operserv/*" + servers = "*" + channels = "*" + users = "connect disconnect" + } +--- +# UnrealIRCd PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: unrealircd-data + namespace: irc +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi +--- +# Anope PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: anope-data + namespace: irc +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi +--- +# UnrealIRCd Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: unrealircd + namespace: irc + labels: + app: unrealircd +spec: + replicas: 1 + selector: + matchLabels: + app: unrealircd + template: + metadata: + labels: + app: unrealircd + spec: + initContainers: + - name: inject-credentials + image: busybox:1.36 + command: ["sh", "-c"] + args: + - | + OPER_PW=$(cat /secrets/password) + LINK_PW=$(cat /secrets/Link-Password) + sed -e "s|__OPER_PASSWORD__|${OPER_PW}|g" \ + -e "s|__LINK_PASSWORD__|${LINK_PW}|g" \ + /config-template/unrealircd.conf > /injected-config/unrealircd.conf + echo "Credentials injected into unrealircd.conf" + volumeMounts: + - name: irc-credentials + mountPath: /secrets + readOnly: true + - name: unrealircd-config-template + mountPath: /config-template + readOnly: true + - name: injected-config + mountPath: /injected-config + - name: copy-tls + image: busybox:1.36 + command: ["sh", "-c"] + args: + - | + cp /tls-secret/tls.crt /tls/server.cert.pem + cp /tls-secret/tls.key /tls/server.key.pem + chmod 644 /tls/server.cert.pem + chmod 644 /tls/server.key.pem + chown 1000:1000 /tls/server.cert.pem /tls/server.key.pem 2>/dev/null || true + chmod 777 /data + volumeMounts: + - name: irc-tls-secret + mountPath: /tls-secret + readOnly: true + - name: irc-tls + mountPath: /tls + - name: unrealircd-data + mountPath: /data + containers: + - name: unrealircd + image: djlegolas/unrealircd:6.1.9.1 + ports: + - containerPort: 6667 + name: irc-plain + - containerPort: 6697 + name: irc-tls + - containerPort: 8067 + name: services-link + volumeMounts: + - name: injected-config + mountPath: /app/conf/unrealircd.conf + subPath: unrealircd.conf + - name: unrealircd-data + mountPath: /app/data + - name: irc-tls + mountPath: /app/conf/tls + resources: + requests: + memory: 64Mi + cpu: 50m + limits: + memory: 256Mi + cpu: 250m + volumes: + - name: irc-credentials + secret: + secretName: irc-credentials + - name: unrealircd-config-template + configMap: + name: unrealircd-config-template + - name: injected-config + emptyDir: {} + - name: unrealircd-data + persistentVolumeClaim: + claimName: unrealircd-data + - name: irc-tls-secret + secret: + secretName: irc-tls + - name: irc-tls + emptyDir: {} +--- +# Anope IRC Services Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anope + namespace: irc + labels: + app: anope +spec: + replicas: 1 + selector: + matchLabels: + app: anope + template: + metadata: + labels: + app: anope + spec: + initContainers: + - name: inject-credentials + image: busybox:1.36 + command: ["sh", "-c"] + args: + - | + LINK_PW=$(cat /secrets/Link-Password) + sed -e "s|__LINK_PASSWORD__|${LINK_PW}|g" \ + /config-template/services.conf > /injected-config/services.conf + echo "Credentials injected into services.conf" + volumeMounts: + - name: irc-credentials + mountPath: /secrets + readOnly: true + - name: anope-config-template + mountPath: /config-template + readOnly: true + - name: injected-config + mountPath: /injected-config + - name: fix-perms + image: busybox:1.36 + command: ["sh", "-c"] + args: + - | + mkdir -p /data/db /data/logs /data/runtime + touch /data/anope.db /data/services.motd + chmod 666 /data/anope.db + chown -R 10000:10000 /data 2>/dev/null || chmod -R 777 /data + echo "Anope data dir prepared: $(ls -la /data/anope.db)" + volumeMounts: + - name: anope-data + mountPath: /data + containers: + - name: anope + image: anope/anope:latest + volumeMounts: + - name: injected-config + mountPath: /anope/conf/services.conf + subPath: services.conf + - name: anope-data + mountPath: /anope/data + resources: + requests: + memory: 64Mi + cpu: 25m + limits: + memory: 128Mi + cpu: 100m + volumes: + - name: irc-credentials + secret: + secretName: irc-credentials + - name: anope-config-template + configMap: + name: anope-config-template + - name: injected-config + emptyDir: {} + - name: anope-data + persistentVolumeClaim: + claimName: anope-data +--- +# UnrealIRCd Service +apiVersion: v1 +kind: Service +metadata: + name: unrealircd + namespace: irc +spec: + selector: + app: unrealircd + ports: + - port: 6667 + targetPort: 6667 + name: irc-plain + - port: 6697 + targetPort: 6697 + name: irc-tls + - port: 8067 + targetPort: 8067 + name: services-link +--- +# Anope Service +apiVersion: v1 +kind: Service +metadata: + name: anope + namespace: irc +spec: + selector: + app: anope + ports: + - port: 8067 + targetPort: 8067 + name: services-link +--- +# Traefik IngressRouteTCP - IRC plain (6667) +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: irc-plain + namespace: irc +spec: + entryPoints: + - irc + routes: + - match: HostSNI(`*`) + services: + - name: unrealircd + port: 6667 +--- +# Traefik IngressRouteTCP - IRC TLS passthrough (6697) +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: irc-tls + namespace: irc +spec: + entryPoints: + - irctls + routes: + - match: HostSNI(`*`) + services: + - name: unrealircd + port: 6697 + tls: + passthrough: true diff --git a/apps/mail/mail.yaml b/apps/mail/mail.yaml index 789ed87..150f625 100644 --- a/apps/mail/mail.yaml +++ b/apps/mail/mail.yaml @@ -1,259 +1,259 @@ -# docker-mailserver - Postfix + Dovecot + rspamd -# ArgoCD managed - BlueJay Lab -# Credentials: 1Password → OnePasswordItem CRD → K8s Secret ---- -apiVersion: v1 -kind: Namespace -metadata: - name: mail - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# 1Password → K8s Secret sync for mail credentials -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: mail-credentials - namespace: mail -spec: - itemPath: "vaults/IAmWorkin/items/Mail Postmaster" ---- -# Mail data PVC -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: mail-data - namespace: mail -spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 5Gi ---- -# Mail state PVC -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: mail-state - namespace: mail -spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 1Gi ---- -# docker-mailserver Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailserver - namespace: mail - labels: - app: mailserver -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: mailserver - template: - metadata: - labels: - app: mailserver - spec: - hostname: mail - initContainers: - - name: inject-accounts - image: busybox:1.36 - command: - - sh - - -c - - | - ADMIN_EMAIL=$(cat /credentials/Admin-Email) - ADMIN_HASH=$(cat /credentials/Admin-Hash) - NOREPLY_EMAIL=$(cat /credentials/Noreply-Email) - NOREPLY_HASH=$(cat /credentials/Noreply-Hash) - echo "${ADMIN_EMAIL}|${ADMIN_HASH}" > /accounts/postfix-accounts.cf - echo "${NOREPLY_EMAIL}|${NOREPLY_HASH}" >> /accounts/postfix-accounts.cf - volumeMounts: - - name: mail-credentials - mountPath: /credentials - readOnly: true - - name: mail-accounts-generated - mountPath: /accounts - containers: - - name: mailserver - image: docker.io/mailserver/docker-mailserver:latest - ports: - - containerPort: 25 - name: smtp - - containerPort: 465 - name: smtps - - containerPort: 587 - name: submission - - containerPort: 143 - name: imap - - containerPort: 993 - name: imaps - env: - - name: ENABLE_SPAMASSASSIN - value: "1" - - name: ENABLE_CLAMAV - value: "0" - - name: ENABLE_RSPAMD - value: "1" - - name: TZ - value: America/Chicago - - name: POSTMASTER_ADDRESS - value: postmaster@iamwork.in - - name: OVERRIDE_HOSTNAME - value: mail.iamwork.in - - name: ENABLE_FAIL2BAN - value: "0" - - name: ENABLE_POSTGREY - value: "0" - - name: ONE_DIR - value: "1" - - name: PERMIT_DOCKER - value: network - - name: SSL_TYPE - value: manual - - name: SSL_CERT_PATH - value: /etc/ssl/mail/tls.crt - - name: SSL_KEY_PATH - value: /etc/ssl/mail/tls.key - - name: ACCOUNT_PROVISIONER - value: FILE - volumeMounts: - - name: mail-data - mountPath: /var/mail - - name: mail-state - mountPath: /var/mail-state - - name: mail-tls - mountPath: /etc/ssl/mail - readOnly: true - - name: mail-accounts-generated - mountPath: /tmp/docker-mailserver/postfix-accounts.cf - subPath: postfix-accounts.cf - readOnly: true - resources: - requests: - memory: 512Mi - cpu: 200m - limits: - memory: 2Gi - cpu: "1" - securityContext: - capabilities: - add: - - NET_ADMIN - - SYS_PTRACE - volumes: - - name: mail-data - persistentVolumeClaim: - claimName: mail-data - - name: mail-state - persistentVolumeClaim: - claimName: mail-state - - name: mail-tls - secret: - secretName: mail-tls - - name: mail-credentials - secret: - secretName: mail-credentials - - name: mail-accounts-generated - emptyDir: {} ---- -# SMTP LoadBalancer Service (external) -apiVersion: v1 -kind: Service -metadata: - name: mail-smtp - namespace: mail - annotations: - metallb.universe.tf/loadBalancerIPs: 10.0.56.202 -spec: - type: LoadBalancer - selector: - app: mailserver - ports: - - port: 25 - targetPort: 25 - name: smtp - protocol: TCP - - port: 465 - targetPort: 465 - name: smtps - protocol: TCP - - port: 587 - targetPort: 587 - name: submission - protocol: TCP ---- -# IMAP ClusterIP Service (internal) -apiVersion: v1 -kind: Service -metadata: - name: mail-imap - namespace: mail -spec: - selector: - app: mailserver - ports: - - port: 143 - targetPort: 143 - name: imap - - port: 993 - targetPort: 993 - name: imaps ---- -# TLS Certificate via cert-manager -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: mail-tls - namespace: mail -spec: - secretName: mail-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - mail.iamworkin.lan ---- -# Traefik IngressRoute - Webmail placeholder -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: mail-webmail - namespace: mail -spec: - entryPoints: - - websecure - routes: - - match: Host(`mail.iamworkin.lan`) - kind: Rule - services: - - name: mail-imap - port: 993 - tls: - secretName: mail-tls ---- -# Public IngressRoute - Webmail (flowercore.io with Cloudflare origin cert) -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: mail-webmail-public - namespace: mail -spec: - entryPoints: - - websecure - routes: - - match: Host(`webmail.flowercore.io`) - kind: Rule - services: - - name: mail-webmail - port: 8080 - tls: - secretName: cf-origin-flowercore-io +# docker-mailserver - Postfix + Dovecot + rspamd +# ArgoCD managed - BlueJay Lab +# Credentials: 1Password → OnePasswordItem CRD → K8s Secret +--- +apiVersion: v1 +kind: Namespace +metadata: + name: mail + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# 1Password → K8s Secret sync for mail credentials +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: mail-credentials + namespace: mail +spec: + itemPath: "vaults/IAmWorkin/items/Mail Postmaster" +--- +# Mail data PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mail-data + namespace: mail +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 5Gi +--- +# Mail state PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mail-state + namespace: mail +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi +--- +# docker-mailserver Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mailserver + namespace: mail + labels: + app: mailserver +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: mailserver + template: + metadata: + labels: + app: mailserver + spec: + hostname: mail + initContainers: + - name: inject-accounts + image: busybox:1.36 + command: + - sh + - -c + - | + ADMIN_EMAIL=$(cat /credentials/Admin-Email) + ADMIN_HASH=$(cat /credentials/Admin-Hash) + NOREPLY_EMAIL=$(cat /credentials/Noreply-Email) + NOREPLY_HASH=$(cat /credentials/Noreply-Hash) + echo "${ADMIN_EMAIL}|${ADMIN_HASH}" > /accounts/postfix-accounts.cf + echo "${NOREPLY_EMAIL}|${NOREPLY_HASH}" >> /accounts/postfix-accounts.cf + volumeMounts: + - name: mail-credentials + mountPath: /credentials + readOnly: true + - name: mail-accounts-generated + mountPath: /accounts + containers: + - name: mailserver + image: docker.io/mailserver/docker-mailserver:latest + ports: + - containerPort: 25 + name: smtp + - containerPort: 465 + name: smtps + - containerPort: 587 + name: submission + - containerPort: 143 + name: imap + - containerPort: 993 + name: imaps + env: + - name: ENABLE_SPAMASSASSIN + value: "1" + - name: ENABLE_CLAMAV + value: "0" + - name: ENABLE_RSPAMD + value: "1" + - name: TZ + value: America/Chicago + - name: POSTMASTER_ADDRESS + value: postmaster@iamwork.in + - name: OVERRIDE_HOSTNAME + value: mail.iamwork.in + - name: ENABLE_FAIL2BAN + value: "0" + - name: ENABLE_POSTGREY + value: "0" + - name: ONE_DIR + value: "1" + - name: PERMIT_DOCKER + value: network + - name: SSL_TYPE + value: manual + - name: SSL_CERT_PATH + value: /etc/ssl/mail/tls.crt + - name: SSL_KEY_PATH + value: /etc/ssl/mail/tls.key + - name: ACCOUNT_PROVISIONER + value: FILE + volumeMounts: + - name: mail-data + mountPath: /var/mail + - name: mail-state + mountPath: /var/mail-state + - name: mail-tls + mountPath: /etc/ssl/mail + readOnly: true + - name: mail-accounts-generated + mountPath: /tmp/docker-mailserver/postfix-accounts.cf + subPath: postfix-accounts.cf + readOnly: true + resources: + requests: + memory: 512Mi + cpu: 200m + limits: + memory: 2Gi + cpu: "1" + securityContext: + capabilities: + add: + - NET_ADMIN + - SYS_PTRACE + volumes: + - name: mail-data + persistentVolumeClaim: + claimName: mail-data + - name: mail-state + persistentVolumeClaim: + claimName: mail-state + - name: mail-tls + secret: + secretName: mail-tls + - name: mail-credentials + secret: + secretName: mail-credentials + - name: mail-accounts-generated + emptyDir: {} +--- +# SMTP LoadBalancer Service (external) +apiVersion: v1 +kind: Service +metadata: + name: mail-smtp + namespace: mail + annotations: + metallb.universe.tf/loadBalancerIPs: 10.0.56.202 +spec: + type: LoadBalancer + selector: + app: mailserver + ports: + - port: 25 + targetPort: 25 + name: smtp + protocol: TCP + - port: 465 + targetPort: 465 + name: smtps + protocol: TCP + - port: 587 + targetPort: 587 + name: submission + protocol: TCP +--- +# IMAP ClusterIP Service (internal) +apiVersion: v1 +kind: Service +metadata: + name: mail-imap + namespace: mail +spec: + selector: + app: mailserver + ports: + - port: 143 + targetPort: 143 + name: imap + - port: 993 + targetPort: 993 + name: imaps +--- +# TLS Certificate via cert-manager +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: mail-tls + namespace: mail +spec: + secretName: mail-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - mail.iamworkin.lan +--- +# Traefik IngressRoute - Webmail placeholder +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: mail-webmail + namespace: mail +spec: + entryPoints: + - websecure + routes: + - match: Host(`mail.iamworkin.lan`) + kind: Rule + services: + - name: mail-imap + port: 993 + tls: + secretName: mail-tls +--- +# Public IngressRoute - Webmail (flowercore.io with Cloudflare origin cert) +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: mail-webmail-public + namespace: mail +spec: + entryPoints: + - websecure + routes: + - match: Host(`webmail.flowercore.io`) + kind: Rule + services: + - name: mail-webmail + port: 8080 + tls: + secretName: cf-origin-flowercore-io diff --git a/apps/matrix/matrix.yaml b/apps/matrix/matrix.yaml index e1db09a..8adba06 100644 --- a/apps/matrix/matrix.yaml +++ b/apps/matrix/matrix.yaml @@ -1,495 +1,495 @@ -# Matrix Synapse + Element Web -# PostgreSQL 16 + Synapse homeserver + Element Web client -# ArgoCD managed - BlueJay Lab -# DB credentials sourced from 1Password via OnePasswordItem CRD (matrix-credentials) -# Synapse homeserver.yaml DB password injected at runtime via init container ---- -apiVersion: v1 -kind: Namespace -metadata: - name: matrix - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# Synapse homeserver.yaml template ConfigMap -# DB password placeholder __DB_PASSWORD__ is replaced at pod startup by init container -apiVersion: v1 -kind: ConfigMap -metadata: - name: synapse-config - namespace: matrix -data: - homeserver.yaml.template: | - server_name: "iamworkin.lan" - pid_file: /data/homeserver.pid - public_baseurl: "https://matrix.iamworkin.lan/" - listeners: - - port: 8008 - tls: false - type: http - x_forwarded: true - bind_addresses: ["0.0.0.0"] - resources: - - names: [client, federation] - compress: false - database: - name: psycopg2 - args: - user: __DB_USER__ - password: __DB_PASSWORD__ - database: synapse - host: matrix-postgres - port: 5432 - cp_min: 5 - cp_max: 10 - log_config: "/config/log.config" - media_store_path: /data/media_store - registration_shared_secret: "a208f2e4b260f6b7d6ff4566df49c56c8b73fa20b911ce4e617b791ee7868adc" - report_stats: false - macaroon_secret_key: "9964f398e8b48a91469ad419d293c06db4562f49df8cc6e129fb3a801fd9052d" - form_secret: "7b0a9dbaf9ee94450e0b3271c408dfc4d313a55843ce4eec2ac1bb0315ffeb76" - signing_key_path: "/data/signing.key" - trusted_key_servers: - - server_name: "matrix.org" - enable_registration: false - suppress_key_server_warning: true - log.config: | - version: 1 - formatters: - precise: - format: "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s" - handlers: - console: - class: logging.StreamHandler - formatter: precise - loggers: - synapse.storage.SQL: - level: WARNING - root: - level: WARNING - handlers: [console] - disable_existing_loggers: false ---- -# PostgreSQL 16 StatefulSet -# Credentials from 1Password-synced matrix-credentials secret -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: matrix-postgres - namespace: matrix - labels: - app: matrix-postgres -spec: - serviceName: matrix-postgres - replicas: 1 - selector: - matchLabels: - app: matrix-postgres - template: - metadata: - labels: - app: matrix-postgres - spec: - containers: - - name: postgres - image: postgres:16-alpine - ports: - - containerPort: 5432 - name: postgres - env: - - name: POSTGRES_USER - valueFrom: - secretKeyRef: - name: matrix-credentials - key: DB-User - - name: POSTGRES_PASSWORD - valueFrom: - secretKeyRef: - name: matrix-credentials - key: DB-Password - - name: POSTGRES_DB - value: synapse - - name: POSTGRES_INITDB_ARGS - value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" - volumeMounts: - - name: matrix-postgres-data - mountPath: /var/lib/postgresql/data - subPath: pgdata - resources: - requests: - memory: 256Mi - cpu: 100m - limits: - memory: 1Gi - cpu: 500m - livenessProbe: - exec: - command: ["pg_isready", "-U", "synapse"] - initialDelaySeconds: 30 - periodSeconds: 10 - readinessProbe: - exec: - command: ["pg_isready", "-U", "synapse"] - initialDelaySeconds: 5 - periodSeconds: 5 - volumeClaimTemplates: - - metadata: - name: matrix-postgres-data - spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 5Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: matrix-postgres - namespace: matrix -spec: - selector: - app: matrix-postgres - ports: - - port: 5432 - targetPort: 5432 - name: postgres - clusterIP: None ---- -# Synapse Data PVC -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: synapse-data - namespace: matrix -spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 2Gi ---- -# Synapse Deployment -# Init container injects DB credentials from 1Password secret into homeserver.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: synapse - namespace: matrix - labels: - app: synapse -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: synapse - template: - metadata: - labels: - app: synapse - spec: - initContainers: - - name: generate-signing-key - image: matrixdotorg/synapse:latest - securityContext: - runAsUser: 0 - command: ["sh", "-c"] - args: - - | - if [ \! -f /data/signing.key ]; then - python -m synapse.app.homeserver --generate-keys --config-path /config-template/homeserver.yaml.template 2>/dev/null || true - # If key generation fails with template, create a minimal config for key gen - if [ \! -f /data/signing.key ]; then - echo server_name: iamworkin.lan > /tmp/minimal.yaml - echo signing_key_path: /data/signing.key >> /tmp/minimal.yaml - python -c "from signedjson.key import generate_signing_key, write_signing_keys; import sys; key = generate_signing_key(a_auto); write_signing_keys(open(/data/signing.key,w), [key])" 2>/dev/null || true - fi - fi - chown 991:991 /data/signing.key 2>/dev/null || true - chmod 644 /data/signing.key 2>/dev/null || true - mkdir -p /data/media_store - chown -R 991:991 /data 2>/dev/null || true - volumeMounts: - - name: synapse-data - mountPath: /data - - name: synapse-config-template - mountPath: /config-template - - name: inject-credentials - image: busybox:latest - command: ["sh", "-c"] - args: - - | - # Copy template and substitute DB credentials from 1Password secret - cp /config-template/log.config /config/log.config - sed -e "s/__DB_PASSWORD__/${DB_PASSWORD}/g" \ - -e "s/__DB_USER__/${DB_USER}/g" \ - /config-template/homeserver.yaml.template > /config/homeserver.yaml - echo "Credentials injected into homeserver.yaml" - env: - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: matrix-credentials - key: DB-Password - - name: DB_USER - valueFrom: - secretKeyRef: - name: matrix-credentials - key: DB-User - volumeMounts: - - name: synapse-config-template - mountPath: /config-template - - name: synapse-config-rendered - mountPath: /config - containers: - - name: synapse - image: matrixdotorg/synapse:latest - ports: - - containerPort: 8008 - name: http - env: - - name: SYNAPSE_CONFIG_DIR - value: /config - - name: SYNAPSE_CONFIG_PATH - value: /config/homeserver.yaml - volumeMounts: - - name: synapse-data - mountPath: /data - - name: synapse-config-rendered - mountPath: /config - resources: - requests: - memory: 512Mi - cpu: 200m - limits: - memory: 2Gi - cpu: "1" - livenessProbe: - httpGet: - path: /health - port: 8008 - initialDelaySeconds: 60 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /health - port: 8008 - initialDelaySeconds: 30 - periodSeconds: 5 - volumes: - - name: synapse-data - persistentVolumeClaim: - claimName: synapse-data - - name: synapse-config-template - configMap: - name: synapse-config - - name: synapse-config-rendered - emptyDir: {} ---- -apiVersion: v1 -kind: Service -metadata: - name: synapse - namespace: matrix -spec: - selector: - app: synapse - ports: - - port: 8008 - targetPort: 8008 - name: http ---- -# Element Web ConfigMap -apiVersion: v1 -kind: ConfigMap -metadata: - name: element-web-config - namespace: matrix -data: - config.json: | - { - "default_server_config": { - "m.homeserver": { - "base_url": "https://matrix.iamworkin.lan", - "server_name": "iamworkin.lan" - } - }, - "brand": "BlueJay Chat", - "disable_guests": true, - "disable_3pid_login": true - } ---- -# Element Web Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: element-web - namespace: matrix - labels: - app: element-web -spec: - replicas: 1 - selector: - matchLabels: - app: element-web - template: - metadata: - labels: - app: element-web - spec: - enableServiceLinks: false - containers: - - name: element-web - image: vectorim/element-web:latest - ports: - - containerPort: 80 - name: http - volumeMounts: - - name: element-config - mountPath: /app/config.json - subPath: config.json - resources: - requests: - memory: 32Mi - cpu: 10m - limits: - memory: 128Mi - cpu: 100m - livenessProbe: - httpGet: - path: / - port: 80 - initialDelaySeconds: 10 - periodSeconds: 10 - readinessProbe: - httpGet: - path: / - port: 80 - initialDelaySeconds: 5 - periodSeconds: 5 - volumes: - - name: element-config - configMap: - name: element-web-config ---- -apiVersion: v1 -kind: Service -metadata: - name: element-web - namespace: matrix -spec: - selector: - app: element-web - ports: - - port: 80 - targetPort: 80 - name: http ---- -# TLS Certificates via cert-manager -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: matrix-tls - namespace: matrix -spec: - secretName: matrix-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - matrix.iamworkin.lan ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: element-tls - namespace: matrix -spec: - secretName: element-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - element.iamworkin.lan ---- -# Traefik IngressRoute - Synapse -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: synapse - namespace: matrix -spec: - entryPoints: - - websecure - routes: - - match: Host(`matrix.iamworkin.lan`) - kind: Rule - services: - - name: synapse - port: 8008 - tls: - secretName: matrix-tls ---- -# Traefik IngressRoute - Element Web -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: element-web - namespace: matrix -spec: - entryPoints: - - websecure - routes: - - match: Host(`element.iamworkin.lan`) - kind: Rule - services: - - name: element-web - port: 80 - tls: - secretName: element-tls ---- -# 1Password secret sync — creates matrix-credentials K8s Secret -# Fields: DB-User, DB-Password, Registration-Secret, username, password, URL -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: matrix-credentials - namespace: matrix -spec: - itemPath: vaults/IAmWorkin/items/Matrix Synapse ---- -# Public IngressRoute - Element Web (flowercore.io with Cloudflare origin cert) -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: element-public - namespace: matrix -spec: - entryPoints: - - websecure - routes: - - match: Host(`element.flowercore.io`) - kind: Rule - services: - - name: element-web - port: 80 - tls: - secretName: cf-origin-flowercore-io ---- -# Public IngressRoute - Synapse (flowercore.io with Cloudflare origin cert) -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: synapse-public - namespace: matrix -spec: - entryPoints: - - websecure - routes: - - match: Host(`matrix.flowercore.io`) - kind: Rule - services: - - name: synapse - port: 8008 - tls: - secretName: cf-origin-flowercore-io +# Matrix Synapse + Element Web +# PostgreSQL 16 + Synapse homeserver + Element Web client +# ArgoCD managed - BlueJay Lab +# DB credentials sourced from 1Password via OnePasswordItem CRD (matrix-credentials) +# Synapse homeserver.yaml DB password injected at runtime via init container +--- +apiVersion: v1 +kind: Namespace +metadata: + name: matrix + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# Synapse homeserver.yaml template ConfigMap +# DB password placeholder __DB_PASSWORD__ is replaced at pod startup by init container +apiVersion: v1 +kind: ConfigMap +metadata: + name: synapse-config + namespace: matrix +data: + homeserver.yaml.template: | + server_name: "iamworkin.lan" + pid_file: /data/homeserver.pid + public_baseurl: "https://matrix.iamworkin.lan/" + listeners: + - port: 8008 + tls: false + type: http + x_forwarded: true + bind_addresses: ["0.0.0.0"] + resources: + - names: [client, federation] + compress: false + database: + name: psycopg2 + args: + user: __DB_USER__ + password: __DB_PASSWORD__ + database: synapse + host: matrix-postgres + port: 5432 + cp_min: 5 + cp_max: 10 + log_config: "/config/log.config" + media_store_path: /data/media_store + registration_shared_secret: "a208f2e4b260f6b7d6ff4566df49c56c8b73fa20b911ce4e617b791ee7868adc" + report_stats: false + macaroon_secret_key: "9964f398e8b48a91469ad419d293c06db4562f49df8cc6e129fb3a801fd9052d" + form_secret: "7b0a9dbaf9ee94450e0b3271c408dfc4d313a55843ce4eec2ac1bb0315ffeb76" + signing_key_path: "/data/signing.key" + trusted_key_servers: + - server_name: "matrix.org" + enable_registration: false + suppress_key_server_warning: true + log.config: | + version: 1 + formatters: + precise: + format: "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s" + handlers: + console: + class: logging.StreamHandler + formatter: precise + loggers: + synapse.storage.SQL: + level: WARNING + root: + level: WARNING + handlers: [console] + disable_existing_loggers: false +--- +# PostgreSQL 16 StatefulSet +# Credentials from 1Password-synced matrix-credentials secret +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: matrix-postgres + namespace: matrix + labels: + app: matrix-postgres +spec: + serviceName: matrix-postgres + replicas: 1 + selector: + matchLabels: + app: matrix-postgres + template: + metadata: + labels: + app: matrix-postgres + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: matrix-credentials + key: DB-User + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: matrix-credentials + key: DB-Password + - name: POSTGRES_DB + value: synapse + - name: POSTGRES_INITDB_ARGS + value: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C" + volumeMounts: + - name: matrix-postgres-data + mountPath: /var/lib/postgresql/data + subPath: pgdata + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 500m + livenessProbe: + exec: + command: ["pg_isready", "-U", "synapse"] + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: ["pg_isready", "-U", "synapse"] + initialDelaySeconds: 5 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: matrix-postgres-data + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 5Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: matrix-postgres + namespace: matrix +spec: + selector: + app: matrix-postgres + ports: + - port: 5432 + targetPort: 5432 + name: postgres + clusterIP: None +--- +# Synapse Data PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: synapse-data + namespace: matrix +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 2Gi +--- +# Synapse Deployment +# Init container injects DB credentials from 1Password secret into homeserver.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: synapse + namespace: matrix + labels: + app: synapse +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: synapse + template: + metadata: + labels: + app: synapse + spec: + initContainers: + - name: generate-signing-key + image: matrixdotorg/synapse:latest + securityContext: + runAsUser: 0 + command: ["sh", "-c"] + args: + - | + if [ \! -f /data/signing.key ]; then + python -m synapse.app.homeserver --generate-keys --config-path /config-template/homeserver.yaml.template 2>/dev/null || true + # If key generation fails with template, create a minimal config for key gen + if [ \! -f /data/signing.key ]; then + echo server_name: iamworkin.lan > /tmp/minimal.yaml + echo signing_key_path: /data/signing.key >> /tmp/minimal.yaml + python -c "from signedjson.key import generate_signing_key, write_signing_keys; import sys; key = generate_signing_key(a_auto); write_signing_keys(open(/data/signing.key,w), [key])" 2>/dev/null || true + fi + fi + chown 991:991 /data/signing.key 2>/dev/null || true + chmod 644 /data/signing.key 2>/dev/null || true + mkdir -p /data/media_store + chown -R 991:991 /data 2>/dev/null || true + volumeMounts: + - name: synapse-data + mountPath: /data + - name: synapse-config-template + mountPath: /config-template + - name: inject-credentials + image: busybox:latest + command: ["sh", "-c"] + args: + - | + # Copy template and substitute DB credentials from 1Password secret + cp /config-template/log.config /config/log.config + sed -e "s/__DB_PASSWORD__/${DB_PASSWORD}/g" \ + -e "s/__DB_USER__/${DB_USER}/g" \ + /config-template/homeserver.yaml.template > /config/homeserver.yaml + echo "Credentials injected into homeserver.yaml" + env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: matrix-credentials + key: DB-Password + - name: DB_USER + valueFrom: + secretKeyRef: + name: matrix-credentials + key: DB-User + volumeMounts: + - name: synapse-config-template + mountPath: /config-template + - name: synapse-config-rendered + mountPath: /config + containers: + - name: synapse + image: matrixdotorg/synapse:latest + ports: + - containerPort: 8008 + name: http + env: + - name: SYNAPSE_CONFIG_DIR + value: /config + - name: SYNAPSE_CONFIG_PATH + value: /config/homeserver.yaml + volumeMounts: + - name: synapse-data + mountPath: /data + - name: synapse-config-rendered + mountPath: /config + resources: + requests: + memory: 512Mi + cpu: 200m + limits: + memory: 2Gi + cpu: "1" + livenessProbe: + httpGet: + path: /health + port: 8008 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8008 + initialDelaySeconds: 30 + periodSeconds: 5 + volumes: + - name: synapse-data + persistentVolumeClaim: + claimName: synapse-data + - name: synapse-config-template + configMap: + name: synapse-config + - name: synapse-config-rendered + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: synapse + namespace: matrix +spec: + selector: + app: synapse + ports: + - port: 8008 + targetPort: 8008 + name: http +--- +# Element Web ConfigMap +apiVersion: v1 +kind: ConfigMap +metadata: + name: element-web-config + namespace: matrix +data: + config.json: | + { + "default_server_config": { + "m.homeserver": { + "base_url": "https://matrix.iamworkin.lan", + "server_name": "iamworkin.lan" + } + }, + "brand": "BlueJay Chat", + "disable_guests": true, + "disable_3pid_login": true + } +--- +# Element Web Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: element-web + namespace: matrix + labels: + app: element-web +spec: + replicas: 1 + selector: + matchLabels: + app: element-web + template: + metadata: + labels: + app: element-web + spec: + enableServiceLinks: false + containers: + - name: element-web + image: vectorim/element-web:latest + ports: + - containerPort: 80 + name: http + volumeMounts: + - name: element-config + mountPath: /app/config.json + subPath: config.json + resources: + requests: + memory: 32Mi + cpu: 10m + limits: + memory: 128Mi + cpu: 100m + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: element-config + configMap: + name: element-web-config +--- +apiVersion: v1 +kind: Service +metadata: + name: element-web + namespace: matrix +spec: + selector: + app: element-web + ports: + - port: 80 + targetPort: 80 + name: http +--- +# TLS Certificates via cert-manager +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: matrix-tls + namespace: matrix +spec: + secretName: matrix-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - matrix.iamworkin.lan +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: element-tls + namespace: matrix +spec: + secretName: element-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - element.iamworkin.lan +--- +# Traefik IngressRoute - Synapse +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: synapse + namespace: matrix +spec: + entryPoints: + - websecure + routes: + - match: Host(`matrix.iamworkin.lan`) + kind: Rule + services: + - name: synapse + port: 8008 + tls: + secretName: matrix-tls +--- +# Traefik IngressRoute - Element Web +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: element-web + namespace: matrix +spec: + entryPoints: + - websecure + routes: + - match: Host(`element.iamworkin.lan`) + kind: Rule + services: + - name: element-web + port: 80 + tls: + secretName: element-tls +--- +# 1Password secret sync — creates matrix-credentials K8s Secret +# Fields: DB-User, DB-Password, Registration-Secret, username, password, URL +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: matrix-credentials + namespace: matrix +spec: + itemPath: vaults/IAmWorkin/items/Matrix Synapse +--- +# Public IngressRoute - Element Web (flowercore.io with Cloudflare origin cert) +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: element-public + namespace: matrix +spec: + entryPoints: + - websecure + routes: + - match: Host(`element.flowercore.io`) + kind: Rule + services: + - name: element-web + port: 80 + tls: + secretName: cf-origin-flowercore-io +--- +# Public IngressRoute - Synapse (flowercore.io with Cloudflare origin cert) +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: synapse-public + namespace: matrix +spec: + entryPoints: + - websecure + routes: + - match: Host(`matrix.flowercore.io`) + kind: Rule + services: + - name: synapse + port: 8008 + tls: + secretName: cf-origin-flowercore-io diff --git a/apps/noc-services/noc-services.yaml b/apps/noc-services/noc-services.yaml index 8788880..0743be1 100644 --- a/apps/noc-services/noc-services.yaml +++ b/apps/noc-services/noc-services.yaml @@ -1,257 +1,257 @@ -# NOC Services - Traefik IngressRoutes for noc1 services -# Proxies internal .iamworkin.lan hostnames to noc1 (10.0.56.10) via -# headless Service + manual Endpoints (standard K8s external proxy pattern) -# ArgoCD managed - BlueJay Lab ---- -apiVersion: v1 -kind: Namespace -metadata: - name: noc-proxy - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# ============================================================ -# BasicAuth - shared across all NOC proxy IngressRoutes -# ============================================================ -apiVersion: v1 -kind: Secret -metadata: - name: noc-proxy-auth - namespace: noc-proxy -type: Opaque -data: - users: YWRtaW46JDJiJDEwJEZjdlVFNWNpNkxvNi5rZ1k5L3hJV2V5M2tvM3VVY1U5YXJaSlQ4N29ZREtCSi5lNkoucXJD ---- -apiVersion: traefik.io/v1alpha1 -kind: Middleware -metadata: - name: noc-proxy-auth - namespace: noc-proxy -spec: - basicAuth: - secret: noc-proxy-auth ---- -# ============================================================ -# Grafana - noc1:3000 -# ============================================================ -apiVersion: v1 -kind: Service -metadata: - name: grafana-external - namespace: noc-proxy -spec: - ports: - - port: 3000 - targetPort: 3000 - name: http - clusterIP: None ---- -apiVersion: v1 -kind: Endpoints -metadata: - name: grafana-external - namespace: noc-proxy -subsets: - - addresses: - - ip: 10.0.56.10 - ports: - - port: 3000 - name: http ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: grafana-tls - namespace: noc-proxy -spec: - secretName: grafana-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - grafana.iamworkin.lan ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: grafana - namespace: noc-proxy -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`grafana.iamworkin.lan`) - middlewares: - - name: noc-proxy-auth - services: - - name: grafana-external - port: 3000 - tls: - secretName: grafana-tls ---- -# ============================================================ -# Prometheus - noc1:9091 -# ============================================================ -apiVersion: v1 -kind: Service -metadata: - name: prometheus-external - namespace: noc-proxy -spec: - ports: - - port: 9091 - targetPort: 9091 - name: http - clusterIP: None ---- -apiVersion: v1 -kind: Endpoints -metadata: - name: prometheus-external - namespace: noc-proxy -subsets: - - addresses: - - ip: 10.0.56.10 - ports: - - port: 9091 - name: http ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: prometheus-tls - namespace: noc-proxy -spec: - secretName: prometheus-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - prometheus.iamworkin.lan ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: prometheus - namespace: noc-proxy -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`prometheus.iamworkin.lan`) - middlewares: - - name: noc-proxy-auth - services: - - name: prometheus-external - port: 9091 - tls: - secretName: prometheus-tls ---- -# ============================================================ -# Cockpit - noc1:9090 -# ============================================================ -apiVersion: v1 -kind: Service -metadata: - name: cockpit-external - namespace: noc-proxy -spec: - ports: - - port: 9090 - targetPort: 9090 - name: https - clusterIP: None ---- -apiVersion: v1 -kind: Endpoints -metadata: - name: cockpit-external - namespace: noc-proxy -subsets: - - addresses: - - ip: 10.0.56.10 - ports: - - port: 9090 - name: https ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: cockpit-tls - namespace: noc-proxy -spec: - secretName: cockpit-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - cockpit.iamworkin.lan ---- -# Cockpit uses self-signed HTTPS on 9090, so we need a ServersTransport -# to skip backend TLS verification -apiVersion: traefik.io/v1alpha1 -kind: ServersTransport -metadata: - name: cockpit-transport - namespace: noc-proxy -spec: - insecureSkipVerify: true ---- -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: cockpit - namespace: noc-proxy -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`cockpit.iamworkin.lan`) - middlewares: - - name: noc-proxy-auth - services: - - name: cockpit-external - port: 9090 - serversTransport: cockpit-transport - tls: - secretName: cockpit-tls ---- -# NetworkPolicy: allow Traefik ingress, allow egress to noc1 -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: noc-proxy-netpol - namespace: noc-proxy -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress - ingress: - - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: traefik-system - egress: - - to: - - ipBlock: - cidr: 10.0.56.10/32 - ports: - - port: 3000 - protocol: TCP - - port: 9090 - protocol: TCP - - port: 9091 - protocol: TCP - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system - ports: - - port: 53 - protocol: UDP - - port: 53 - protocol: TCP +# NOC Services - Traefik IngressRoutes for noc1 services +# Proxies internal .iamworkin.lan hostnames to noc1 (10.0.56.10) via +# headless Service + manual Endpoints (standard K8s external proxy pattern) +# ArgoCD managed - BlueJay Lab +--- +apiVersion: v1 +kind: Namespace +metadata: + name: noc-proxy + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# ============================================================ +# BasicAuth - shared across all NOC proxy IngressRoutes +# ============================================================ +apiVersion: v1 +kind: Secret +metadata: + name: noc-proxy-auth + namespace: noc-proxy +type: Opaque +data: + users: YWRtaW46JDJiJDEwJEZjdlVFNWNpNkxvNi5rZ1k5L3hJV2V5M2tvM3VVY1U5YXJaSlQ4N29ZREtCSi5lNkoucXJD +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: noc-proxy-auth + namespace: noc-proxy +spec: + basicAuth: + secret: noc-proxy-auth +--- +# ============================================================ +# Grafana - noc1:3000 +# ============================================================ +apiVersion: v1 +kind: Service +metadata: + name: grafana-external + namespace: noc-proxy +spec: + ports: + - port: 3000 + targetPort: 3000 + name: http + clusterIP: None +--- +apiVersion: v1 +kind: Endpoints +metadata: + name: grafana-external + namespace: noc-proxy +subsets: + - addresses: + - ip: 10.0.56.10 + ports: + - port: 3000 + name: http +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: grafana-tls + namespace: noc-proxy +spec: + secretName: grafana-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - grafana.iamworkin.lan +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: grafana + namespace: noc-proxy +spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`grafana.iamworkin.lan`) + middlewares: + - name: noc-proxy-auth + services: + - name: grafana-external + port: 3000 + tls: + secretName: grafana-tls +--- +# ============================================================ +# Prometheus - noc1:9091 +# ============================================================ +apiVersion: v1 +kind: Service +metadata: + name: prometheus-external + namespace: noc-proxy +spec: + ports: + - port: 9091 + targetPort: 9091 + name: http + clusterIP: None +--- +apiVersion: v1 +kind: Endpoints +metadata: + name: prometheus-external + namespace: noc-proxy +subsets: + - addresses: + - ip: 10.0.56.10 + ports: + - port: 9091 + name: http +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: prometheus-tls + namespace: noc-proxy +spec: + secretName: prometheus-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - prometheus.iamworkin.lan +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: prometheus + namespace: noc-proxy +spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`prometheus.iamworkin.lan`) + middlewares: + - name: noc-proxy-auth + services: + - name: prometheus-external + port: 9091 + tls: + secretName: prometheus-tls +--- +# ============================================================ +# Cockpit - noc1:9090 +# ============================================================ +apiVersion: v1 +kind: Service +metadata: + name: cockpit-external + namespace: noc-proxy +spec: + ports: + - port: 9090 + targetPort: 9090 + name: https + clusterIP: None +--- +apiVersion: v1 +kind: Endpoints +metadata: + name: cockpit-external + namespace: noc-proxy +subsets: + - addresses: + - ip: 10.0.56.10 + ports: + - port: 9090 + name: https +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: cockpit-tls + namespace: noc-proxy +spec: + secretName: cockpit-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - cockpit.iamworkin.lan +--- +# Cockpit uses self-signed HTTPS on 9090, so we need a ServersTransport +# to skip backend TLS verification +apiVersion: traefik.io/v1alpha1 +kind: ServersTransport +metadata: + name: cockpit-transport + namespace: noc-proxy +spec: + insecureSkipVerify: true +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: cockpit + namespace: noc-proxy +spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`cockpit.iamworkin.lan`) + middlewares: + - name: noc-proxy-auth + services: + - name: cockpit-external + port: 9090 + serversTransport: cockpit-transport + tls: + secretName: cockpit-tls +--- +# NetworkPolicy: allow Traefik ingress, allow egress to noc1 +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: noc-proxy-netpol + namespace: noc-proxy +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik-system + egress: + - to: + - ipBlock: + cidr: 10.0.56.10/32 + ports: + - port: 3000 + protocol: TCP + - port: 9090 + protocol: TCP + - port: 9091 + protocol: TCP + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/apps/teamspeak/teamspeak.yaml b/apps/teamspeak/teamspeak.yaml index cfeec85..a773fdc 100644 --- a/apps/teamspeak/teamspeak.yaml +++ b/apps/teamspeak/teamspeak.yaml @@ -1,145 +1,145 @@ -# TeamSpeak 3 Server -# ArgoCD managed - BlueJay Lab ---- -apiVersion: v1 -kind: Namespace -metadata: - name: teamspeak - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# 1Password secret sync - TeamSpeak credentials -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: teamspeak-credentials - namespace: teamspeak -spec: - itemPath: "vaults/IAmWorkin/items/TeamSpeak 3" ---- -# TeamSpeak data PVC -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: teamspeak-data - namespace: teamspeak -spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 1Gi ---- -# TeamSpeak license key -apiVersion: v1 -kind: Secret -metadata: - name: teamspeak-license - namespace: teamspeak -type: Opaque -data: - licensekey.dat: Y29tcGFueSBuYW1lIDogQW5kcmV3IFN0b2x0egphZGRyZXNzICAgICAgOiA2NjI1IDE2Mm5kIHQKemlwY29kZSAgICAgIDogNTUwNjgKY2l0eSAgICAgICAgIDogTGFrZXZpbGxlCmNvdW50cnkgICAgICA6IFVuaXRlZCBTdGF0ZXMgb2YgQW1lcmljYQpwaG9uZSAgICAgICAgOiA5NTI5OTk2NDExCmZheCAgICAgICAgICA6IApzYWxlcyBjb250YWN0OiBBbmRyZXcgU3RvbHR6IChhc3RvbHR6QGlhbXdvcmsuaW4pCnRlY2ggY29udGFjdCA6IEFuZHJldyBTdG9sdHogKGFzdG9sdHpAaWFtd29yay5pbikKCnRzIHZlcnNpb24gICA6IDMKdHlwZSAgICAgICAgIDogQWN0aXZhdGlvbiBMaWNlbnNlCnN0YXJ0IGRhdGUgICA6IFN1biBNYXIgIDggMDA6MDA6MDAgMjAyNgplbmQgZGF0ZSAgICAgOiBNb24gTWFyICA4IDAwOjAwOjAwIDIwMjcKbWF4LiB2aXJ0dWFsIHNlcnZlcnM6IDEKbWF4LiBzbG90cyAgIDogMzIKZGVzY3JpcHRpb24gIDogVGVhbVNwZWFrIDMgQUwKCgo9PWtleTI9PQpDb0VDQ3NnQkFRQ3ZiSEZUUURZL3RlclBlaWxycC9FQ1U5eENINVUzeEM5MmxZVE5hWS8wS1FBSkZ1ZUFhemJzZ0FBQUFDVlVaV0Z0VTNCbFlXc2dVM2x6ZEdWdGN5QkhiV0pJQUFCTnozMW0vRVdKU2w4QjFtcjJ2anZvdjRIL2s2UE9KVGJLb2NzVnoxRTNCUUFZd1c3S1B0R0k4QUFBQUNSVVpXRnRVM0JsWVdzZ2MzbHpkR1Z0Y3lCSGJXSklBQUFXSlVXQkErRjQzTERsMzFxS0NpOWw4WWdpQmhyOXlIWW1rbTFvWVVTMlhnSVl5cFVBR3F2SWdBWUFBQUFBUVc1a2NtVjNJRk4wYjJ4MGVnQVNJTmp2OStDVDQwTTUvQWdlY0lLcW1Gb2hyZnAzd3dSbGVSL25Ia3kvdHRKcEdDQWdBU29PVkdWaGJWTndaV0ZySURNZ1FVd1N0UUVCQUs5c2NWTkFOaisxNnM5NktXdW44UUpUM0VJZmxUZkVMM2FWaE0xcGovUXBBQWtXNTRCck51eUFBQUFBSlZSbFlXMVRjR1ZoYXlCVGVYTjBaVzF6SUVkdFlrZ0FBRTNQZldiOFJZbEtYd0hXYXZhK08raS9nZitUbzg0bE5zcWh5eFhQVVRjRkFCakJic28rMFlqd0FBQUFKRlJsWVcxVGNHVmhheUJ6ZVhOMFpXMXpJRWR0WWtnQUFGTTFQT2YxY0VzclhjckFtMU9wS1RnN2cyaHlVNlROY093TFVJaXJpbnc4QlJqQmJzbyswWWp3R2tBRFpWRFRLVndEQTdqYWVFK0pqRFQ1WUFJc1hScWxpdlpTeTR1aUJSYlB0RUZ3d0VnVXR2RHg4TEJaQ29zOEhPTDI0bllBalZ0UFRCdHJnRWF0NHVBSgo9PWtleTI9PQoKPT1rZXk9PQpWRk16VEdsalpXNXpaV1l3WkFJd1JCL1VCTWQ0MnRnQmg4TkY0dnN4K2lsTmpFZHQzazdxbXZPamRYZUVtUWFjQ0J5S0tnelZ4T2MwMDlYanIrU09BakFjR2oxV1ZwTGpiNGxGYk1DaEpsU1FXZW5zYTJhNmJXK2JoN2lCdzF1Zk5WaXFnTk41YThpN1VFS29sNmhsSmExVFViQkVpTng2T1NjbHBBOENhNDhjVk5zS050N0wvandDaVhuZFBkV3BuUW5CdUlYeXFLUXkxM2ZsNDJWUWo0Rk90V2kzTHRoYkNpODlWWEQwYXRpNTNjQlRTWHN4QXdMUndzZFBqeWFobmFsNStXaiswUFdYOE4ySlEzMFZmalFQVnAyMFk2dmc0K29lcm1vV291QUc5RDFjQzRrQVJoQnlVRmU5VnUvM2VBTVRiNUlHbnllY1k0QUF0RjJmdTR1aG5NYUFYQ3Q3UWNHN1JSVEFLQnNjSVhyVlVLM1NnS3ZLR1p6T2RGeU0= ---- -# TeamSpeak 3 Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: teamspeak - namespace: teamspeak - labels: - app: teamspeak -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: teamspeak - template: - metadata: - labels: - app: teamspeak - spec: - initContainers: - - name: copy-license - image: busybox:latest - command: ['sh', '-c', 'cp /license/licensekey.dat /data/licensekey.dat'] - volumeMounts: - - name: teamspeak-data - mountPath: /data - - name: license - mountPath: /license - readOnly: true - containers: - - name: teamspeak - image: teamspeak:latest - ports: - - containerPort: 9987 - name: voice - protocol: UDP - - containerPort: 30033 - name: filetransfer - protocol: TCP - - containerPort: 10011 - name: serverquery - protocol: TCP - env: - - name: TS3SERVER_LICENSE - value: accept - - name: TS3SERVER_SERVERADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: teamspeak-credentials - key: ServerQuery-Password - volumeMounts: - - name: teamspeak-data - mountPath: /var/ts3server - resources: - requests: - memory: 128Mi - cpu: 50m - limits: - memory: 512Mi - cpu: 500m - readinessProbe: - tcpSocket: - port: 10011 - initialDelaySeconds: 30 - periodSeconds: 10 - livenessProbe: - tcpSocket: - port: 10011 - initialDelaySeconds: 60 - periodSeconds: 15 - volumes: - - name: teamspeak-data - persistentVolumeClaim: - claimName: teamspeak-data - - name: license - secret: - secretName: teamspeak-license ---- -# TeamSpeak LoadBalancer Service -apiVersion: v1 -kind: Service -metadata: - name: teamspeak - namespace: teamspeak - annotations: - metallb.universe.tf/loadBalancerIPs: 10.0.56.205 -spec: - type: LoadBalancer - selector: - app: teamspeak - ports: - - port: 9987 - targetPort: 9987 - name: voice - protocol: UDP - - port: 30033 - targetPort: 30033 - name: filetransfer - protocol: TCP - - port: 10011 - targetPort: 10011 - name: serverquery - protocol: TCP +# TeamSpeak 3 Server +# ArgoCD managed - BlueJay Lab +--- +apiVersion: v1 +kind: Namespace +metadata: + name: teamspeak + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# 1Password secret sync - TeamSpeak credentials +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: teamspeak-credentials + namespace: teamspeak +spec: + itemPath: "vaults/IAmWorkin/items/TeamSpeak 3" +--- +# TeamSpeak data PVC +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: teamspeak-data + namespace: teamspeak +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi +--- +# TeamSpeak license key +apiVersion: v1 +kind: Secret +metadata: + name: teamspeak-license + namespace: teamspeak +type: Opaque +data: + licensekey.dat: Y29tcGFueSBuYW1lIDogQW5kcmV3IFN0b2x0egphZGRyZXNzICAgICAgOiA2NjI1IDE2Mm5kIHQKemlwY29kZSAgICAgIDogNTUwNjgKY2l0eSAgICAgICAgIDogTGFrZXZpbGxlCmNvdW50cnkgICAgICA6IFVuaXRlZCBTdGF0ZXMgb2YgQW1lcmljYQpwaG9uZSAgICAgICAgOiA5NTI5OTk2NDExCmZheCAgICAgICAgICA6IApzYWxlcyBjb250YWN0OiBBbmRyZXcgU3RvbHR6IChhc3RvbHR6QGlhbXdvcmsuaW4pCnRlY2ggY29udGFjdCA6IEFuZHJldyBTdG9sdHogKGFzdG9sdHpAaWFtd29yay5pbikKCnRzIHZlcnNpb24gICA6IDMKdHlwZSAgICAgICAgIDogQWN0aXZhdGlvbiBMaWNlbnNlCnN0YXJ0IGRhdGUgICA6IFN1biBNYXIgIDggMDA6MDA6MDAgMjAyNgplbmQgZGF0ZSAgICAgOiBNb24gTWFyICA4IDAwOjAwOjAwIDIwMjcKbWF4LiB2aXJ0dWFsIHNlcnZlcnM6IDEKbWF4LiBzbG90cyAgIDogMzIKZGVzY3JpcHRpb24gIDogVGVhbVNwZWFrIDMgQUwKCgo9PWtleTI9PQpDb0VDQ3NnQkFRQ3ZiSEZUUURZL3RlclBlaWxycC9FQ1U5eENINVUzeEM5MmxZVE5hWS8wS1FBSkZ1ZUFhemJzZ0FBQUFDVlVaV0Z0VTNCbFlXc2dVM2x6ZEdWdGN5QkhiV0pJQUFCTnozMW0vRVdKU2w4QjFtcjJ2anZvdjRIL2s2UE9KVGJLb2NzVnoxRTNCUUFZd1c3S1B0R0k4QUFBQUNSVVpXRnRVM0JsWVdzZ2MzbHpkR1Z0Y3lCSGJXSklBQUFXSlVXQkErRjQzTERsMzFxS0NpOWw4WWdpQmhyOXlIWW1rbTFvWVVTMlhnSVl5cFVBR3F2SWdBWUFBQUFBUVc1a2NtVjNJRk4wYjJ4MGVnQVNJTmp2OStDVDQwTTUvQWdlY0lLcW1Gb2hyZnAzd3dSbGVSL25Ia3kvdHRKcEdDQWdBU29PVkdWaGJWTndaV0ZySURNZ1FVd1N0UUVCQUs5c2NWTkFOaisxNnM5NktXdW44UUpUM0VJZmxUZkVMM2FWaE0xcGovUXBBQWtXNTRCck51eUFBQUFBSlZSbFlXMVRjR1ZoYXlCVGVYTjBaVzF6SUVkdFlrZ0FBRTNQZldiOFJZbEtYd0hXYXZhK08raS9nZitUbzg0bE5zcWh5eFhQVVRjRkFCakJic28rMFlqd0FBQUFKRlJsWVcxVGNHVmhheUJ6ZVhOMFpXMXpJRWR0WWtnQUFGTTFQT2YxY0VzclhjckFtMU9wS1RnN2cyaHlVNlROY093TFVJaXJpbnc4QlJqQmJzbyswWWp3R2tBRFpWRFRLVndEQTdqYWVFK0pqRFQ1WUFJc1hScWxpdlpTeTR1aUJSYlB0RUZ3d0VnVXR2RHg4TEJaQ29zOEhPTDI0bllBalZ0UFRCdHJnRWF0NHVBSgo9PWtleTI9PQoKPT1rZXk9PQpWRk16VEdsalpXNXpaV1l3WkFJd1JCL1VCTWQ0MnRnQmg4TkY0dnN4K2lsTmpFZHQzazdxbXZPamRYZUVtUWFjQ0J5S0tnelZ4T2MwMDlYanIrU09BakFjR2oxV1ZwTGpiNGxGYk1DaEpsU1FXZW5zYTJhNmJXK2JoN2lCdzF1Zk5WaXFnTk41YThpN1VFS29sNmhsSmExVFViQkVpTng2T1NjbHBBOENhNDhjVk5zS050N0wvandDaVhuZFBkV3BuUW5CdUlYeXFLUXkxM2ZsNDJWUWo0Rk90V2kzTHRoYkNpODlWWEQwYXRpNTNjQlRTWHN4QXdMUndzZFBqeWFobmFsNStXaiswUFdYOE4ySlEzMFZmalFQVnAyMFk2dmc0K29lcm1vV291QUc5RDFjQzRrQVJoQnlVRmU5VnUvM2VBTVRiNUlHbnllY1k0QUF0RjJmdTR1aG5NYUFYQ3Q3UWNHN1JSVEFLQnNjSVhyVlVLM1NnS3ZLR1p6T2RGeU0= +--- +# TeamSpeak 3 Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: teamspeak + namespace: teamspeak + labels: + app: teamspeak +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: teamspeak + template: + metadata: + labels: + app: teamspeak + spec: + initContainers: + - name: copy-license + image: busybox:latest + command: ['sh', '-c', 'cp /license/licensekey.dat /data/licensekey.dat'] + volumeMounts: + - name: teamspeak-data + mountPath: /data + - name: license + mountPath: /license + readOnly: true + containers: + - name: teamspeak + image: teamspeak:latest + ports: + - containerPort: 9987 + name: voice + protocol: UDP + - containerPort: 30033 + name: filetransfer + protocol: TCP + - containerPort: 10011 + name: serverquery + protocol: TCP + env: + - name: TS3SERVER_LICENSE + value: accept + - name: TS3SERVER_SERVERADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: teamspeak-credentials + key: ServerQuery-Password + volumeMounts: + - name: teamspeak-data + mountPath: /var/ts3server + resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 512Mi + cpu: 500m + readinessProbe: + tcpSocket: + port: 10011 + initialDelaySeconds: 30 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 10011 + initialDelaySeconds: 60 + periodSeconds: 15 + volumes: + - name: teamspeak-data + persistentVolumeClaim: + claimName: teamspeak-data + - name: license + secret: + secretName: teamspeak-license +--- +# TeamSpeak LoadBalancer Service +apiVersion: v1 +kind: Service +metadata: + name: teamspeak + namespace: teamspeak + annotations: + metallb.universe.tf/loadBalancerIPs: 10.0.56.205 +spec: + type: LoadBalancer + selector: + app: teamspeak + ports: + - port: 9987 + targetPort: 9987 + name: voice + protocol: UDP + - port: 30033 + targetPort: 30033 + name: filetransfer + protocol: TCP + - port: 10011 + targetPort: 10011 + name: serverquery + protocol: TCP diff --git a/apps/voice/voice.yaml b/apps/voice/voice.yaml index 1041542..5bbf22b 100644 --- a/apps/voice/voice.yaml +++ b/apps/voice/voice.yaml @@ -1,127 +1,127 @@ -# Twilio Voice Bridge - Traefik ingress to edge1 -# ArgoCD managed - BlueJay Lab -# Routes voice.bluejay.dev (TwiML) and voice-ws.bluejay.dev (WebSocket) -# to edge1 Pi5 at 10.0.57.15 (PROD VLAN) ---- -apiVersion: v1 -kind: Namespace -metadata: - name: voice - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# Cloudflare origin cert for *.bluejay.dev -apiVersion: v1 -kind: Secret -metadata: - name: cf-origin-bluejay-dev - namespace: voice -type: kubernetes.io/tls -data: - tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVvakNDQTRxZ0F3SUJBZ0lVTkxnemZ4UVRzMElyWWRaZUZKUGN5TjRyNmFjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZc3hDekFKQmdOVkJBWVRBbFZUTVJrd0Z3WURWUVFLRXhCRGJHOTFaRVpzWVhKbExDQkpibU11TVRRdwpNZ1lEVlFRTEV5dERiRzkxWkVac1lYSmxJRTl5YVdkcGJpQlRVMHdnUTJWeWRHbG1hV05oZEdVZ1FYVjBhRzl5CmFYUjVNUll3RkFZRFZRUUhFdzFUWVc0Z1JuSmhibU5wYzJOdk1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGgKTUI0WERUSTJNRE14TURBMU1USXdNRm9YRFRReE1ETXdOakExTVRJd01Gb3dZakVaTUJjR0ExVUVDaE1RUTJ4dgpkV1JHYkdGeVpTd2dTVzVqTGpFZE1Cc0dBMVVFQ3hNVVEyeHZkV1JHYkdGeVpTQlBjbWxuYVc0Z1EwRXhKakFrCkJnTlZCQU1USFVOc2IzVmtSbXhoY21VZ1QzSnBaMmx1SUVObGNuUnBabWxqWVhSbE1JSUJJakFOQmdrcWhraUcKOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXlPODBJQ3dPV0RWTEpQNm9RenI3aXVENmtWMGNWZ01VbUx5VApKVnVYUlhZSEY3M2ZrM2pPbytCQVE1M2pmbERHUFVYc0UvNlV6VDRoUDVYTlVLaUNXaitvYy84eE1BSWsxcWwrClZnbFI1MDBUQ0FtZDliazNZVkxiZjBSejVMMUQ0WGJmOEVzamhOUVV2Z3Y0dTZoQzdnRmdrVGplc1dIZjg0K04KNERETDdmTjFQZHR4RVBiVWZrbGN1MUZSdXdlMk9QNkFEMlJvdkphNWZwODRHcVY2TDAzdjY2RjFtMnBST1VmRwpFdWpRNG4zSms2cUx5NHZTTENzOGJlOGRBRW5QcDgyZ2NRZk9mUVlIS2JTUWhiQnMwK01vK3lkTHpHSzFRdklRCnVPcDZRT1BtM0lac09Eb0VCdG5kMTh2amx6Y1JSdE94cjNzaFovdmNWY0o0YUJNZDVRSURBUUFCbzRJQkpEQ0MKQVNBd0RnWURWUjBQQVFIL0JBUURBZ1dnTUIwR0ExVWRKUVFXTUJRR0NDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjRApBVEFNQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCVHgyYmFCUGlvWjZUd2U3THhnaTViUS82cUFkakFmCkJnTlZIU01FR0RBV2dCUWs2Rk5YWFh3MFFJZXA2NVRidXVFV2VQd3BwREJBQmdnckJnRUZCUWNCQVFRME1ESXcKTUFZSUt3WUJCUVVITUFHR0pHaDBkSEE2THk5dlkzTndMbU5zYjNWa1pteGhjbVV1WTI5dEwyOXlhV2RwYmw5agpZVEFsQmdOVkhSRUVIakFjZ2cwcUxtSnNkV1ZxWVhrdVpHVjJnZ3RpYkhWbGFtRjVMbVJsZGpBNEJnTlZIUjhFCk1UQXZNQzJnSzZBcGhpZG9kSFJ3T2k4dlkzSnNMbU5zYjNWa1pteGhjbVV1WTI5dEwyOXlhV2RwYmw5allTNWoKY213d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDS0ZVRXhYbFB5KzlPRytJc1VWN1NXS09Udkk0b2JrUkd6bwpOckhudWZ3dGtxa0dzUGErUU5LbUk1UVNaYTVMS1YybWZJdWsrNE12U1FvZklmbUFUb1JEWEdQK041aWxEbWRSCk5rS2ttSUpZL242UzM3MGdZN0JQMjFwNjJKUnZkVUU5ZmV5RU1iMUdNbGNINjN6MUQxMzZZOWlvQ1FnYWNFZVUKOFZSVWFPZkJvby9sVzlYbXA1ZDZzcTBic2tybUhRN1ZSTjUxZCtsL0RvY2lkU2xZcHQxbXljSUN3c1F4U0dpMApYN1pMTXhHdCtDVG9jcFRFbkdrQ2t0NnhrKzVJUElXaHYvVnZuTnlQNUwxM0ZRN1d4QzFsaUIwcVdKMkEwcWpoCkR2cmxPNUpsclNWWEtNc0hwSWRFN3pJZ29JUUc2cnU1N1V4UjJyeko0d0VrSXcrd1ZyMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= - tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRREk3elFnTEE1WU5Vc2sKL3FoRE92dUs0UHFSWFJ4V0F4U1l2Sk1sVzVkRmRnY1h2ZCtUZU02ajRFQkRuZU4rVU1ZOVJld1QvcFROUGlFLwpsYzFRcUlKYVA2aHovekV3QWlUV3FYNVdDVkhuVFJNSUNaMzF1VGRoVXR0L1JIUGt2VVBoZHQvd1N5T0UxQlMrCkMvaTdxRUx1QVdDUk9ONnhZZC96ajQzZ01NdnQ4M1U5MjNFUTl0UitTVnk3VVZHN0I3WTQvb0FQWkdpOGxybCsKbnpnYXBYb3ZUZS9yb1hXYmFsRTVSOFlTNk5EaWZjbVRxb3ZMaTlJc0t6eHQ3eDBBU2MrbnphQnhCODU5QmdjcAp0SkNGc0d6VDR5ajdKMHZNWXJWQzhoQzQ2bnBBNCtiY2htdzRPZ1FHMmQzWHkrT1hOeEZHMDdHdmV5Rm4rOXhWCnduaG9FeDNsQWdNQkFBRUNnZ0VBRFpWeU9peVFTYkZNbzdNZGovSDRXR0t1UGM2RUlHSno3WUZ1Rnl2eWRZMHQKbkpMRy94Sy9NWC96Q0Q4dnhuWFNlUWoxbFVKMEw4M2Y5SXI5aHRMbGdSRmxvM1hnanVUT05iN2VuaFZpTnBkVQp6b25MNW5VL2c3SlV5VzFJd25GekdkWnQvREl3TkFZY1l0NnZVWXhsL2U0VTU2eG5EYW5XdUlIL2J1VU5uRWZtCjZNcnlIblhseDA0TzZzOElLSCtXNUxDam1ma1Jac0VveE9Damt6T2hucmdCTk4xSWxWSWhhMDhLOXBxRk5qM0wKaWd4MVNYZDhqaHNZUHJxaDNkak0rVUNKSitKMURuMjhaUmoyZUtqWDMvZmFDbGFpOEFzUVBtQTJScjZYZ3kzeApaR090dzRPL2Nxb1hnOUFDU05NanRNK3VtaXM3QzNncWp6VUZ4MWwvNFFLQmdRRFNvSGVDY1dXSUJQV3grR01hCnBSMENHejQ0eDNPRUhnNWYza2RvK3BDK0I3RnYxREV5bktqUUY4VFBYaWJXVGZVL1pBRG5DVmIvU1Z2L1QyNnkKT2NlaW44UGRqdXkraE05ZUxjWXU4QVZiK0VKOHFMUUsrZE5QZm1nYXZJMWR2V3U2Tm8wSTNSSzZWUkpiQXEyWgpLcjdsVEloMjdZMDRMajlaaUIxU01YZ0J0UUtCZ1FEME9EbEVCNHRHa1l2dTVLMkFoaDBmdnlIaUVoUTd5dWUzCmZXdG1VYnRxV0ZlM1R5UG9JTWJWMTM3MkNIdlh6MFNEMXRwYzJxa3hxN3c4d1BYM3RJZGk5NWxMbVMzZm8zWFoKdTNTYVo4enozTlAwR3JXK1Y0c3hHb1VnbWgwL2lzOXJoaWxXZGF5bU5SWEQ5U0MyUU5IdTU5NDY2UjFkczNnbgpiZlJlUkw4SmNRS0JnREptVjNLTk0rQmlYM0Jnb1VaRThEWUswczYvV3pMb0JrU0dhY3dDK1JPZnY2T2t3TWo5Cmw1K0RzSUoyWXhDd3d0aVNVMnoxWFMzbEhmQnZ6MnN5VEVUcnVmQ1FQTEl5RVhUVnV6Q01HcHd4UWFlV3JzNVoKaldqZU5JY0JTMHA5QXdRaC9ZbDdiUG5OVllFVm1QaW5zOW9tZ0JrRkt0K2dvV1FKSUFzRTcxUnBBb0dCQUxNSgphTW43c2RuNUo1bnA0VnhBZGFkcGFvQ2VlbURmUG9KaEd0UTNCT3RRZW5Xek9nS1p6TXJHSVpoaTNjOTNicVlzClk0Y0E4bHFzcU9IdElDVUpIdHVwNHFMdVdCZ0VjSWcvaVpzTWo4OFRTL3MvZlk5ZUJIZnFGa0N4V3RIVGhINHkKSzZucnVMZGNZV2w0RWhRcWJ2enkxUk5oQkp0Rno4Y3dMNTdRVFRDeEFvR0FjZFFkbzk3bzZmS0dQa3kvREpnNApoTGlLbHVkTjF3bmJaTHFVM0UwNzBwVDhJQkx3TFNpTUNpYXRXWWtScHFpdUQyMSsya1p4SE1NdnNoZWJXQmFmCmVqWU9jcHVvQ3VWdWc1K1dlbmc0cmUxSTl2c2czUE5WYkdrNW1xNmZDbndsbGFnMkEvSVBVRFFOaUpCVGM1WUQKc25udzhVTjNBTFBMSTlPVVd1eXJUckk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K ---- -# Service (no selector) pointing to external edge1 -apiVersion: v1 -kind: Service -metadata: - name: voice-bridge - namespace: voice -spec: - ports: - - name: twiml - port: 8766 - targetPort: 8766 - protocol: TCP - - name: websocket - port: 8765 - targetPort: 8765 - protocol: TCP ---- -# Manual Endpoints for edge1 (outside K8s) -apiVersion: v1 -kind: Endpoints -metadata: - name: voice-bridge - namespace: voice -subsets: - - addresses: - - ip: 10.0.57.15 - ports: - - name: twiml - port: 8766 - protocol: TCP - - name: websocket - port: 8765 - protocol: TCP ---- -# TwiML webhook: voice.bluejay.dev -> edge1:8766 -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: voice-twiml - namespace: voice -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`voice.bluejay.dev`) - services: - - name: voice-bridge - port: 8766 - tls: - secretName: cf-origin-bluejay-dev ---- -# WebSocket media stream: voice-ws.bluejay.dev -> edge1:8765 -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: voice-ws - namespace: voice -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`voice-ws.bluejay.dev`) - services: - - name: voice-bridge - port: 8765 - tls: - secretName: cf-origin-bluejay-dev ---- -# NetworkPolicy: allow Traefik ingress only -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: voice-netpol - namespace: voice -spec: - podSelector: {} - policyTypes: - - Ingress - - Egress - ingress: - - from: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: traefik-system - egress: - - to: - - ipBlock: - cidr: 10.0.57.15/32 - ports: - - port: 8765 - protocol: TCP - - port: 8766 - protocol: TCP - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system - ports: - - port: 53 - protocol: UDP - - port: 53 - protocol: TCP +# Twilio Voice Bridge - Traefik ingress to edge1 +# ArgoCD managed - BlueJay Lab +# Routes voice.bluejay.dev (TwiML) and voice-ws.bluejay.dev (WebSocket) +# to edge1 Pi5 at 10.0.57.15 (PROD VLAN) +--- +apiVersion: v1 +kind: Namespace +metadata: + name: voice + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# Cloudflare origin cert for *.bluejay.dev +apiVersion: v1 +kind: Secret +metadata: + name: cf-origin-bluejay-dev + namespace: voice +type: kubernetes.io/tls +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVvakNDQTRxZ0F3SUJBZ0lVTkxnemZ4UVRzMElyWWRaZUZKUGN5TjRyNmFjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd2dZc3hDekFKQmdOVkJBWVRBbFZUTVJrd0Z3WURWUVFLRXhCRGJHOTFaRVpzWVhKbExDQkpibU11TVRRdwpNZ1lEVlFRTEV5dERiRzkxWkVac1lYSmxJRTl5YVdkcGJpQlRVMHdnUTJWeWRHbG1hV05oZEdVZ1FYVjBhRzl5CmFYUjVNUll3RkFZRFZRUUhFdzFUWVc0Z1JuSmhibU5wYzJOdk1STXdFUVlEVlFRSUV3cERZV3hwWm05eWJtbGgKTUI0WERUSTJNRE14TURBMU1USXdNRm9YRFRReE1ETXdOakExTVRJd01Gb3dZakVaTUJjR0ExVUVDaE1RUTJ4dgpkV1JHYkdGeVpTd2dTVzVqTGpFZE1Cc0dBMVVFQ3hNVVEyeHZkV1JHYkdGeVpTQlBjbWxuYVc0Z1EwRXhKakFrCkJnTlZCQU1USFVOc2IzVmtSbXhoY21VZ1QzSnBaMmx1SUVObGNuUnBabWxqWVhSbE1JSUJJakFOQmdrcWhraUcKOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXlPODBJQ3dPV0RWTEpQNm9RenI3aXVENmtWMGNWZ01VbUx5VApKVnVYUlhZSEY3M2ZrM2pPbytCQVE1M2pmbERHUFVYc0UvNlV6VDRoUDVYTlVLaUNXaitvYy84eE1BSWsxcWwrClZnbFI1MDBUQ0FtZDliazNZVkxiZjBSejVMMUQ0WGJmOEVzamhOUVV2Z3Y0dTZoQzdnRmdrVGplc1dIZjg0K04KNERETDdmTjFQZHR4RVBiVWZrbGN1MUZSdXdlMk9QNkFEMlJvdkphNWZwODRHcVY2TDAzdjY2RjFtMnBST1VmRwpFdWpRNG4zSms2cUx5NHZTTENzOGJlOGRBRW5QcDgyZ2NRZk9mUVlIS2JTUWhiQnMwK01vK3lkTHpHSzFRdklRCnVPcDZRT1BtM0lac09Eb0VCdG5kMTh2amx6Y1JSdE94cjNzaFovdmNWY0o0YUJNZDVRSURBUUFCbzRJQkpEQ0MKQVNBd0RnWURWUjBQQVFIL0JBUURBZ1dnTUIwR0ExVWRKUVFXTUJRR0NDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjRApBVEFNQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCVHgyYmFCUGlvWjZUd2U3THhnaTViUS82cUFkakFmCkJnTlZIU01FR0RBV2dCUWs2Rk5YWFh3MFFJZXA2NVRidXVFV2VQd3BwREJBQmdnckJnRUZCUWNCQVFRME1ESXcKTUFZSUt3WUJCUVVITUFHR0pHaDBkSEE2THk5dlkzTndMbU5zYjNWa1pteGhjbVV1WTI5dEwyOXlhV2RwYmw5agpZVEFsQmdOVkhSRUVIakFjZ2cwcUxtSnNkV1ZxWVhrdVpHVjJnZ3RpYkhWbGFtRjVMbVJsZGpBNEJnTlZIUjhFCk1UQXZNQzJnSzZBcGhpZG9kSFJ3T2k4dlkzSnNMbU5zYjNWa1pteGhjbVV1WTI5dEwyOXlhV2RwYmw5allTNWoKY213d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFDS0ZVRXhYbFB5KzlPRytJc1VWN1NXS09Udkk0b2JrUkd6bwpOckhudWZ3dGtxa0dzUGErUU5LbUk1UVNaYTVMS1YybWZJdWsrNE12U1FvZklmbUFUb1JEWEdQK041aWxEbWRSCk5rS2ttSUpZL242UzM3MGdZN0JQMjFwNjJKUnZkVUU5ZmV5RU1iMUdNbGNINjN6MUQxMzZZOWlvQ1FnYWNFZVUKOFZSVWFPZkJvby9sVzlYbXA1ZDZzcTBic2tybUhRN1ZSTjUxZCtsL0RvY2lkU2xZcHQxbXljSUN3c1F4U0dpMApYN1pMTXhHdCtDVG9jcFRFbkdrQ2t0NnhrKzVJUElXaHYvVnZuTnlQNUwxM0ZRN1d4QzFsaUIwcVdKMkEwcWpoCkR2cmxPNUpsclNWWEtNc0hwSWRFN3pJZ29JUUc2cnU1N1V4UjJyeko0d0VrSXcrd1ZyMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRREk3elFnTEE1WU5Vc2sKL3FoRE92dUs0UHFSWFJ4V0F4U1l2Sk1sVzVkRmRnY1h2ZCtUZU02ajRFQkRuZU4rVU1ZOVJld1QvcFROUGlFLwpsYzFRcUlKYVA2aHovekV3QWlUV3FYNVdDVkhuVFJNSUNaMzF1VGRoVXR0L1JIUGt2VVBoZHQvd1N5T0UxQlMrCkMvaTdxRUx1QVdDUk9ONnhZZC96ajQzZ01NdnQ4M1U5MjNFUTl0UitTVnk3VVZHN0I3WTQvb0FQWkdpOGxybCsKbnpnYXBYb3ZUZS9yb1hXYmFsRTVSOFlTNk5EaWZjbVRxb3ZMaTlJc0t6eHQ3eDBBU2MrbnphQnhCODU5QmdjcAp0SkNGc0d6VDR5ajdKMHZNWXJWQzhoQzQ2bnBBNCtiY2htdzRPZ1FHMmQzWHkrT1hOeEZHMDdHdmV5Rm4rOXhWCnduaG9FeDNsQWdNQkFBRUNnZ0VBRFpWeU9peVFTYkZNbzdNZGovSDRXR0t1UGM2RUlHSno3WUZ1Rnl2eWRZMHQKbkpMRy94Sy9NWC96Q0Q4dnhuWFNlUWoxbFVKMEw4M2Y5SXI5aHRMbGdSRmxvM1hnanVUT05iN2VuaFZpTnBkVQp6b25MNW5VL2c3SlV5VzFJd25GekdkWnQvREl3TkFZY1l0NnZVWXhsL2U0VTU2eG5EYW5XdUlIL2J1VU5uRWZtCjZNcnlIblhseDA0TzZzOElLSCtXNUxDam1ma1Jac0VveE9Damt6T2hucmdCTk4xSWxWSWhhMDhLOXBxRk5qM0wKaWd4MVNYZDhqaHNZUHJxaDNkak0rVUNKSitKMURuMjhaUmoyZUtqWDMvZmFDbGFpOEFzUVBtQTJScjZYZ3kzeApaR090dzRPL2Nxb1hnOUFDU05NanRNK3VtaXM3QzNncWp6VUZ4MWwvNFFLQmdRRFNvSGVDY1dXSUJQV3grR01hCnBSMENHejQ0eDNPRUhnNWYza2RvK3BDK0I3RnYxREV5bktqUUY4VFBYaWJXVGZVL1pBRG5DVmIvU1Z2L1QyNnkKT2NlaW44UGRqdXkraE05ZUxjWXU4QVZiK0VKOHFMUUsrZE5QZm1nYXZJMWR2V3U2Tm8wSTNSSzZWUkpiQXEyWgpLcjdsVEloMjdZMDRMajlaaUIxU01YZ0J0UUtCZ1FEME9EbEVCNHRHa1l2dTVLMkFoaDBmdnlIaUVoUTd5dWUzCmZXdG1VYnRxV0ZlM1R5UG9JTWJWMTM3MkNIdlh6MFNEMXRwYzJxa3hxN3c4d1BYM3RJZGk5NWxMbVMzZm8zWFoKdTNTYVo4enozTlAwR3JXK1Y0c3hHb1VnbWgwL2lzOXJoaWxXZGF5bU5SWEQ5U0MyUU5IdTU5NDY2UjFkczNnbgpiZlJlUkw4SmNRS0JnREptVjNLTk0rQmlYM0Jnb1VaRThEWUswczYvV3pMb0JrU0dhY3dDK1JPZnY2T2t3TWo5Cmw1K0RzSUoyWXhDd3d0aVNVMnoxWFMzbEhmQnZ6MnN5VEVUcnVmQ1FQTEl5RVhUVnV6Q01HcHd4UWFlV3JzNVoKaldqZU5JY0JTMHA5QXdRaC9ZbDdiUG5OVllFVm1QaW5zOW9tZ0JrRkt0K2dvV1FKSUFzRTcxUnBBb0dCQUxNSgphTW43c2RuNUo1bnA0VnhBZGFkcGFvQ2VlbURmUG9KaEd0UTNCT3RRZW5Xek9nS1p6TXJHSVpoaTNjOTNicVlzClk0Y0E4bHFzcU9IdElDVUpIdHVwNHFMdVdCZ0VjSWcvaVpzTWo4OFRTL3MvZlk5ZUJIZnFGa0N4V3RIVGhINHkKSzZucnVMZGNZV2w0RWhRcWJ2enkxUk5oQkp0Rno4Y3dMNTdRVFRDeEFvR0FjZFFkbzk3bzZmS0dQa3kvREpnNApoTGlLbHVkTjF3bmJaTHFVM0UwNzBwVDhJQkx3TFNpTUNpYXRXWWtScHFpdUQyMSsya1p4SE1NdnNoZWJXQmFmCmVqWU9jcHVvQ3VWdWc1K1dlbmc0cmUxSTl2c2czUE5WYkdrNW1xNmZDbndsbGFnMkEvSVBVRFFOaUpCVGM1WUQKc25udzhVTjNBTFBMSTlPVVd1eXJUckk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +--- +# Service (no selector) pointing to external edge1 +apiVersion: v1 +kind: Service +metadata: + name: voice-bridge + namespace: voice +spec: + ports: + - name: twiml + port: 8766 + targetPort: 8766 + protocol: TCP + - name: websocket + port: 8765 + targetPort: 8765 + protocol: TCP +--- +# Manual Endpoints for edge1 (outside K8s) +apiVersion: v1 +kind: Endpoints +metadata: + name: voice-bridge + namespace: voice +subsets: + - addresses: + - ip: 10.0.57.15 + ports: + - name: twiml + port: 8766 + protocol: TCP + - name: websocket + port: 8765 + protocol: TCP +--- +# TwiML webhook: voice.bluejay.dev -> edge1:8766 +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: voice-twiml + namespace: voice +spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`voice.bluejay.dev`) + services: + - name: voice-bridge + port: 8766 + tls: + secretName: cf-origin-bluejay-dev +--- +# WebSocket media stream: voice-ws.bluejay.dev -> edge1:8765 +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: voice-ws + namespace: voice +spec: + entryPoints: + - websecure + routes: + - kind: Rule + match: Host(`voice-ws.bluejay.dev`) + services: + - name: voice-bridge + port: 8765 + tls: + secretName: cf-origin-bluejay-dev +--- +# NetworkPolicy: allow Traefik ingress only +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: voice-netpol + namespace: voice +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik-system + egress: + - to: + - ipBlock: + cidr: 10.0.57.15/32 + ports: + - port: 8765 + protocol: TCP + - port: 8766 + protocol: TCP + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP diff --git a/apps/zabbix/zabbix.yaml b/apps/zabbix/zabbix.yaml index 4a6053b..2503d7a 100644 --- a/apps/zabbix/zabbix.yaml +++ b/apps/zabbix/zabbix.yaml @@ -1,360 +1,360 @@ -# Zabbix 7.2 Monitoring Stack -# PostgreSQL 16 + Zabbix Server + Zabbix Web (nginx) -# ArgoCD managed - BlueJay Lab -# Credentials sourced from 1Password via OnePasswordItem CRD (zabbix-credentials) ---- -apiVersion: v1 -kind: Namespace -metadata: - name: zabbix - labels: - app.kubernetes.io/part-of: bluejay-infra ---- -# PostgreSQL 16 StatefulSet -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: zabbix-postgres - namespace: zabbix - labels: - app: zabbix-postgres -spec: - serviceName: zabbix-postgres - replicas: 1 - selector: - matchLabels: - app: zabbix-postgres - template: - metadata: - labels: - app: zabbix-postgres - spec: - containers: - - name: postgres - image: postgres:16-alpine - args: - - "-c" - - "shared_buffers=256MB" - - "-c" - - "effective_cache_size=512MB" - - "-c" - - "work_mem=16MB" - - "-c" - - "maintenance_work_mem=128MB" - - "-c" - - "random_page_cost=1.1" - - "-c" - - "effective_io_concurrency=200" - - "-c" - - "max_connections=50" - - "-c" - - "checkpoint_completion_target=0.9" - - "-c" - - "wal_buffers=8MB" - ports: - - containerPort: 5432 - name: postgres - env: - - name: POSTGRES_USER - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: DB-User - - name: POSTGRES_PASSWORD - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: DB-Password - - name: POSTGRES_DB - value: zabbix - volumeMounts: - - name: zabbix-postgres-data - mountPath: /var/lib/postgresql/data - subPath: pgdata - resources: - requests: - memory: 512Mi - cpu: 200m - limits: - memory: 1Gi - cpu: "1" - livenessProbe: - exec: - command: - - pg_isready - - -U - - zabbix - initialDelaySeconds: 30 - periodSeconds: 10 - readinessProbe: - exec: - command: - - pg_isready - - -U - - zabbix - initialDelaySeconds: 5 - periodSeconds: 5 - volumeClaimTemplates: - - metadata: - name: zabbix-postgres-data - spec: - accessModes: [ReadWriteOnce] - resources: - requests: - storage: 10Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: zabbix-postgres - namespace: zabbix -spec: - selector: - app: zabbix-postgres - ports: - - port: 5432 - targetPort: 5432 - name: postgres - clusterIP: None ---- -# Zabbix Server -apiVersion: apps/v1 -kind: Deployment -metadata: - name: zabbix-server - namespace: zabbix - labels: - app: zabbix-server -spec: - replicas: 1 - selector: - matchLabels: - app: zabbix-server - template: - metadata: - labels: - app: zabbix-server - spec: - containers: - - name: zabbix-server - image: zabbix/zabbix-server-pgsql:7.2-alpine-latest - ports: - - containerPort: 10051 - name: trapper - env: - - name: DB_SERVER_HOST - value: zabbix-postgres - - name: DB_SERVER_PORT - value: "5432" - - name: POSTGRES_USER - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: DB-User - - name: POSTGRES_PASSWORD - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: DB-Password - - name: POSTGRES_DB - value: zabbix - - name: ZBX_CACHESIZE - value: "64M" - - name: ZBX_VALUECACHESIZE - value: "64M" - - name: ZBX_HISTORYCACHESIZE - value: "32M" - - name: ZBX_TRENDCACHESIZE - value: "8M" - - name: ZBX_STARTPOLLERS - value: "10" - - name: ZBX_STARTPOLLERSUNREACHABLE - value: "3" - resources: - requests: - memory: 256Mi - cpu: 100m - limits: - memory: 1Gi - cpu: "1" - livenessProbe: - tcpSocket: - port: 10051 - initialDelaySeconds: 60 - periodSeconds: 10 - readinessProbe: - tcpSocket: - port: 10051 - initialDelaySeconds: 30 - periodSeconds: 5 ---- -apiVersion: v1 -kind: Service -metadata: - name: zabbix-server - namespace: zabbix -spec: - selector: - app: zabbix-server - ports: - - port: 10051 - targetPort: 10051 - name: trapper ---- -apiVersion: v1 -kind: Service -metadata: - name: zabbix-trapper - namespace: zabbix - annotations: - metallb.universe.tf/loadBalancerIPs: 10.0.56.203 -spec: - type: LoadBalancer - selector: - app: zabbix-server - ports: - - port: 10051 - targetPort: 10051 - name: trapper - protocol: TCP ---- -# Zabbix Web (nginx + PostgreSQL) -apiVersion: apps/v1 -kind: Deployment -metadata: - name: zabbix-web - namespace: zabbix - labels: - app: zabbix-web -spec: - replicas: 1 - selector: - matchLabels: - app: zabbix-web - template: - metadata: - labels: - app: zabbix-web - spec: - containers: - - name: zabbix-web - image: zabbix/zabbix-web-nginx-pgsql:7.2-alpine-latest - ports: - - containerPort: 8080 - name: http - env: - - name: ZBX_SERVER_HOST - value: zabbix-server - - name: ZBX_SERVER_NAME - value: "BlueJay NOC" - - name: PHP_TZ - value: America/Chicago - - name: DB_SERVER_HOST - value: zabbix-postgres - - name: DB_SERVER_PORT - value: "5432" - - name: POSTGRES_USER - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: DB-User - - name: POSTGRES_PASSWORD - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: DB-Password - - name: POSTGRES_DB - value: zabbix - - name: ZBX_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: zabbix-credentials - key: password - - name: ZBX_MEMORYLIMIT - value: "256M" - - name: PHP_FPM_PM_MAX_CHILDREN - value: "10" - - name: PHP_FPM_PM_START_SERVERS - value: "3" - - name: PHP_FPM_PM_MIN_SPARE_SERVERS - value: "2" - - name: PHP_FPM_PM_MAX_SPARE_SERVERS - value: "5" - - name: PHP_FPM_PM_MAX_REQUESTS - value: "500" - resources: - requests: - memory: 256Mi - cpu: 100m - limits: - memory: 768Mi - cpu: 500m - livenessProbe: - httpGet: - path: / - port: 8080 - initialDelaySeconds: 60 - timeoutSeconds: 5 - periodSeconds: 10 - readinessProbe: - httpGet: - path: / - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 5 - timeoutSeconds: 5 ---- -apiVersion: v1 -kind: Service -metadata: - name: zabbix-web - namespace: zabbix -spec: - selector: - app: zabbix-web - ports: - - port: 8080 - targetPort: 8080 - name: http ---- -# TLS Certificate via cert-manager -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: zabbix-tls - namespace: zabbix -spec: - secretName: zabbix-tls - issuerRef: - name: step-ca-acme - kind: ClusterIssuer - dnsNames: - - zabbix.iamworkin.lan ---- -# Traefik IngressRoute -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: zabbix-web - namespace: zabbix -spec: - entryPoints: - - websecure - routes: - - match: Host(`zabbix.iamworkin.lan`) - kind: Rule - services: - - name: zabbix-web - port: 8080 - tls: - secretName: zabbix-tls ---- -# 1Password secret sync — creates zabbix-credentials K8s Secret -# Fields: DB-User, DB-Password, username, password, URL -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: zabbix-credentials - namespace: zabbix -spec: - itemPath: vaults/IAmWorkin/items/Zabbix Admin +# Zabbix 7.2 Monitoring Stack +# PostgreSQL 16 + Zabbix Server + Zabbix Web (nginx) +# ArgoCD managed - BlueJay Lab +# Credentials sourced from 1Password via OnePasswordItem CRD (zabbix-credentials) +--- +apiVersion: v1 +kind: Namespace +metadata: + name: zabbix + labels: + app.kubernetes.io/part-of: bluejay-infra +--- +# PostgreSQL 16 StatefulSet +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: zabbix-postgres + namespace: zabbix + labels: + app: zabbix-postgres +spec: + serviceName: zabbix-postgres + replicas: 1 + selector: + matchLabels: + app: zabbix-postgres + template: + metadata: + labels: + app: zabbix-postgres + spec: + containers: + - name: postgres + image: postgres:16-alpine + args: + - "-c" + - "shared_buffers=256MB" + - "-c" + - "effective_cache_size=512MB" + - "-c" + - "work_mem=16MB" + - "-c" + - "maintenance_work_mem=128MB" + - "-c" + - "random_page_cost=1.1" + - "-c" + - "effective_io_concurrency=200" + - "-c" + - "max_connections=50" + - "-c" + - "checkpoint_completion_target=0.9" + - "-c" + - "wal_buffers=8MB" + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: DB-User + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: DB-Password + - name: POSTGRES_DB + value: zabbix + volumeMounts: + - name: zabbix-postgres-data + mountPath: /var/lib/postgresql/data + subPath: pgdata + resources: + requests: + memory: 512Mi + cpu: 200m + limits: + memory: 1Gi + cpu: "1" + livenessProbe: + exec: + command: + - pg_isready + - -U + - zabbix + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - zabbix + initialDelaySeconds: 5 + periodSeconds: 5 + volumeClaimTemplates: + - metadata: + name: zabbix-postgres-data + spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 10Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: zabbix-postgres + namespace: zabbix +spec: + selector: + app: zabbix-postgres + ports: + - port: 5432 + targetPort: 5432 + name: postgres + clusterIP: None +--- +# Zabbix Server +apiVersion: apps/v1 +kind: Deployment +metadata: + name: zabbix-server + namespace: zabbix + labels: + app: zabbix-server +spec: + replicas: 1 + selector: + matchLabels: + app: zabbix-server + template: + metadata: + labels: + app: zabbix-server + spec: + containers: + - name: zabbix-server + image: zabbix/zabbix-server-pgsql:7.2-alpine-latest + ports: + - containerPort: 10051 + name: trapper + env: + - name: DB_SERVER_HOST + value: zabbix-postgres + - name: DB_SERVER_PORT + value: "5432" + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: DB-User + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: DB-Password + - name: POSTGRES_DB + value: zabbix + - name: ZBX_CACHESIZE + value: "64M" + - name: ZBX_VALUECACHESIZE + value: "64M" + - name: ZBX_HISTORYCACHESIZE + value: "32M" + - name: ZBX_TRENDCACHESIZE + value: "8M" + - name: ZBX_STARTPOLLERS + value: "10" + - name: ZBX_STARTPOLLERSUNREACHABLE + value: "3" + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: "1" + livenessProbe: + tcpSocket: + port: 10051 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 10051 + initialDelaySeconds: 30 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: zabbix-server + namespace: zabbix +spec: + selector: + app: zabbix-server + ports: + - port: 10051 + targetPort: 10051 + name: trapper +--- +apiVersion: v1 +kind: Service +metadata: + name: zabbix-trapper + namespace: zabbix + annotations: + metallb.universe.tf/loadBalancerIPs: 10.0.56.203 +spec: + type: LoadBalancer + selector: + app: zabbix-server + ports: + - port: 10051 + targetPort: 10051 + name: trapper + protocol: TCP +--- +# Zabbix Web (nginx + PostgreSQL) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: zabbix-web + namespace: zabbix + labels: + app: zabbix-web +spec: + replicas: 1 + selector: + matchLabels: + app: zabbix-web + template: + metadata: + labels: + app: zabbix-web + spec: + containers: + - name: zabbix-web + image: zabbix/zabbix-web-nginx-pgsql:7.2-alpine-latest + ports: + - containerPort: 8080 + name: http + env: + - name: ZBX_SERVER_HOST + value: zabbix-server + - name: ZBX_SERVER_NAME + value: "BlueJay NOC" + - name: PHP_TZ + value: America/Chicago + - name: DB_SERVER_HOST + value: zabbix-postgres + - name: DB_SERVER_PORT + value: "5432" + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: DB-User + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: DB-Password + - name: POSTGRES_DB + value: zabbix + - name: ZBX_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: zabbix-credentials + key: password + - name: ZBX_MEMORYLIMIT + value: "256M" + - name: PHP_FPM_PM_MAX_CHILDREN + value: "10" + - name: PHP_FPM_PM_START_SERVERS + value: "3" + - name: PHP_FPM_PM_MIN_SPARE_SERVERS + value: "2" + - name: PHP_FPM_PM_MAX_SPARE_SERVERS + value: "5" + - name: PHP_FPM_PM_MAX_REQUESTS + value: "500" + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 768Mi + cpu: 500m + livenessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 60 + timeoutSeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: zabbix-web + namespace: zabbix +spec: + selector: + app: zabbix-web + ports: + - port: 8080 + targetPort: 8080 + name: http +--- +# TLS Certificate via cert-manager +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: zabbix-tls + namespace: zabbix +spec: + secretName: zabbix-tls + issuerRef: + name: step-ca-acme + kind: ClusterIssuer + dnsNames: + - zabbix.iamworkin.lan +--- +# Traefik IngressRoute +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: zabbix-web + namespace: zabbix +spec: + entryPoints: + - websecure + routes: + - match: Host(`zabbix.iamworkin.lan`) + kind: Rule + services: + - name: zabbix-web + port: 8080 + tls: + secretName: zabbix-tls +--- +# 1Password secret sync — creates zabbix-credentials K8s Secret +# Fields: DB-User, DB-Password, username, password, URL +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: zabbix-credentials + namespace: zabbix +spec: + itemPath: vaults/IAmWorkin/items/Zabbix Admin