Update telephony-web image to v20260324d, resolve merge conflicts
This commit is contained in:
@@ -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: {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>FlowerCore</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #0a1628 0%, #1a2744 50%, #0d1f3c 100%);
|
||||
color: #e0e8f0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
.logo {
|
||||
font-size: 5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
filter: drop-shadow(0 0 20px rgba(74, 158, 255, 0.3));
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
background: linear-gradient(135deg, #4a9eff, #7ab3ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 1.3rem;
|
||||
color: #7ab3ff;
|
||||
font-weight: 300;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
color: #8aa8c4;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 3rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
.services {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
.service {
|
||||
background: rgba(74, 158, 255, 0.08);
|
||||
border: 1px solid rgba(74, 158, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 1.2rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.service:hover {
|
||||
background: rgba(74, 158, 255, 0.15);
|
||||
border-color: rgba(74, 158, 255, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.service h3 { color: #4a9eff; font-size: 0.95rem; margin-bottom: 0.3rem; }
|
||||
.service p { color: #8aa8c4; font-size: 0.8rem; }
|
||||
.status-bar {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem 2rem;
|
||||
background: rgba(74, 158, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(74, 158, 255, 0.1);
|
||||
}
|
||||
.status-item { text-align: center; }
|
||||
.status-item .value { color: #4a9eff; font-size: 1.5rem; font-weight: 700; }
|
||||
.status-item .label { color: #6a8ca4; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 1px; }
|
||||
.footer {
|
||||
margin-top: 3rem;
|
||||
color: #4a6580;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.footer a { color: #4a6580; text-decoration: none; }
|
||||
.footer a:hover { color: #7ab3ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="hero">
|
||||
<div class="logo">🌻</div>
|
||||
<h1>FlowerCore</h1>
|
||||
<p class="subtitle">Blue Jay Lab</p>
|
||||
<p class="description">
|
||||
Multi-tenant service management platform built on .NET 10,
|
||||
Kubernetes, and GitOps. Digital signage, telephony IVR,
|
||||
MySQL/PHP hosting, and infrastructure automation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="services">
|
||||
<a class="service" href="https://gitea.flowercore.io">
|
||||
<h3>Source</h3>
|
||||
<p>Gitea repositories</p>
|
||||
</a>
|
||||
<a class="service" href="https://webmail.flowercore.io">
|
||||
<h3>Mail</h3>
|
||||
<p>Webmail access</p>
|
||||
</a>
|
||||
<a class="service" href="https://element.flowercore.io">
|
||||
<h3>Chat</h3>
|
||||
<p>Matrix messaging</p>
|
||||
</a>
|
||||
<a class="service" href="https://github.com/FlowerCoreIO">
|
||||
<h3>GitHub</h3>
|
||||
<p>Open source</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<div class="status-item">
|
||||
<div class="value">17</div>
|
||||
<div class="label">Services</div>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<div class="value">13</div>
|
||||
<div class="label">VLANs</div>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<div class="value">12k+</div>
|
||||
<div class="label">Tests</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer">
|
||||
FlowerCore · Bare-metal RKE2 · ArgoCD managed
|
||||
· <a href="mailto:admin@flowercore.io">Contact</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
# 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: |
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>FlowerCore</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #0a1628 0%, #1a2744 50%, #0d1f3c 100%);
|
||||
color: #e0e8f0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
max-width: 800px;
|
||||
}
|
||||
.logo {
|
||||
font-size: 5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
filter: drop-shadow(0 0 20px rgba(74, 158, 255, 0.3));
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
background: linear-gradient(135deg, #4a9eff, #7ab3ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 1.3rem;
|
||||
color: #7ab3ff;
|
||||
font-weight: 300;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
color: #8aa8c4;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 3rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
.services {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
.service {
|
||||
background: rgba(74, 158, 255, 0.08);
|
||||
border: 1px solid rgba(74, 158, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 1.2rem;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.service:hover {
|
||||
background: rgba(74, 158, 255, 0.15);
|
||||
border-color: rgba(74, 158, 255, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.service h3 { color: #4a9eff; font-size: 0.95rem; margin-bottom: 0.3rem; }
|
||||
.service p { color: #8aa8c4; font-size: 0.8rem; }
|
||||
.status-bar {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem 2rem;
|
||||
background: rgba(74, 158, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(74, 158, 255, 0.1);
|
||||
}
|
||||
.status-item { text-align: center; }
|
||||
.status-item .value { color: #4a9eff; font-size: 1.5rem; font-weight: 700; }
|
||||
.status-item .label { color: #6a8ca4; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 1px; }
|
||||
.footer {
|
||||
margin-top: 3rem;
|
||||
color: #4a6580;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.footer a { color: #4a6580; text-decoration: none; }
|
||||
.footer a:hover { color: #7ab3ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="hero">
|
||||
<div class="logo">🌻</div>
|
||||
<h1>FlowerCore</h1>
|
||||
<p class="subtitle">Blue Jay Lab</p>
|
||||
<p class="description">
|
||||
Multi-tenant service management platform built on .NET 10,
|
||||
Kubernetes, and GitOps. Digital signage, telephony IVR,
|
||||
MySQL/PHP hosting, and infrastructure automation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="services">
|
||||
<a class="service" href="https://gitea.flowercore.io">
|
||||
<h3>Source</h3>
|
||||
<p>Gitea repositories</p>
|
||||
</a>
|
||||
<a class="service" href="https://webmail.flowercore.io">
|
||||
<h3>Mail</h3>
|
||||
<p>Webmail access</p>
|
||||
</a>
|
||||
<a class="service" href="https://element.flowercore.io">
|
||||
<h3>Chat</h3>
|
||||
<p>Matrix messaging</p>
|
||||
</a>
|
||||
<a class="service" href="https://github.com/FlowerCoreIO">
|
||||
<h3>GitHub</h3>
|
||||
<p>Open source</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<div class="status-item">
|
||||
<div class="value">17</div>
|
||||
<div class="label">Services</div>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<div class="value">13</div>
|
||||
<div class="label">VLANs</div>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<div class="value">12k+</div>
|
||||
<div class="label">Tests</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="footer">
|
||||
FlowerCore · Bare-metal RKE2 · ArgoCD managed
|
||||
· <a href="mailto:admin@flowercore.io">Contact</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
# 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because one or more lines are too long
1362
apps/irc/irc.yaml
1362
apps/irc/irc.yaml
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user