jitsi setup

This commit is contained in:
Brad Stein 2025-09-07 13:20:49 -05:00
parent 8207ef10d1
commit 001e9c36fe
16 changed files with 486 additions and 0 deletions

0
-c
View File

View File

@ -0,0 +1,19 @@
# infrastructure/flux-system/kustomization-jitsi.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: jitsi
namespace: flux-system
spec:
interval: 10m
path: ./services/jitsi
targetNamespace: jitsi
prune: true
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system
dependsOn:
- name: core
wait: true
timeout: 5m

View File

@ -1,3 +1,4 @@
# infrastructure/flux-system/kustomization-longhorn-ui.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:

View File

@ -9,6 +9,7 @@ resources:
- kustomization-helm.yaml
- kustomization-gitea.yaml
- kustomization-vault.yaml
- kustomization-jitsi.yaml
- kustomization-crypto.yaml
- kustomization-monerod.yaml
- kustomization-jellyfin.yaml

View File

@ -0,0 +1,169 @@
# services/jitsi/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jitsi-prosody
namespace: jitsi
spec:
replicas: 1
selector:
matchLabels: { app: jitsi-prosody }
template:
metadata:
labels: { app: jitsi-prosody }
spec:
nodeSelector:
kubernetes.io/hostname: titan-22 # keep everything on titan-22 for simplicity
kubernetes.io/arch: amd64
containers:
- name: prosody
image: jitsi/prosody:stable
ports:
- { name: c2s, containerPort: 5222, protocol: TCP }
- { name: http, containerPort: 5280, protocol: TCP }
- { name: comp, containerPort: 5347, protocol: TCP }
env:
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
- { name: ENABLE_AUTH, value: "0" } # open instance, no auth (fastest path)
- { name: ENABLE_GUESTS, value: "1" }
- { name: JICOFO_AUTH_USER, value: "focus" }
- { name: JVB_AUTH_USER, value: "jvb" }
- name: JICOFO_AUTH_PASSWORD
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_AUTH_PASSWORD } }
- name: JICOFO_COMPONENT_SECRET
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_COMPONENT_SECRET } }
- name: JVB_AUTH_PASSWORD
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JVB_AUTH_PASSWORD } }
volumeMounts:
- { name: cfg, mountPath: /config }
volumes:
- name: cfg
persistentVolumeClaim: { claimName: jitsi-prosody-config }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jitsi-jicofo
namespace: jitsi
spec:
replicas: 1
selector:
matchLabels: { app: jitsi-jicofo }
template:
metadata:
labels: { app: jitsi-jicofo }
spec:
nodeSelector:
kubernetes.io/hostname: titan-22
kubernetes.io/arch: amd64
containers:
- name: jicofo
image: jitsi/jicofo:stable
env:
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
- { name: XMPP_SERVER, value: "jitsi-prosody.jitsi.svc.cluster.local" }
- { name: JICOFO_AUTH_USER, value: "focus" }
- name: JICOFO_AUTH_PASSWORD
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_AUTH_PASSWORD } }
- name: JICOFO_COMPONENT_SECRET
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JICOFO_COMPONENT_SECRET } }
- { name: JVB_BREWERY_MUC, value: "jvbbrewery" }
volumeMounts:
- { name: cfg, mountPath: /config }
volumes:
- name: cfg
persistentVolumeClaim: { claimName: jitsi-jicofo-config }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jitsi-jvb
namespace: jitsi
spec:
replicas: 1
selector:
matchLabels: { app: jitsi-jvb }
template:
metadata:
labels: { app: jitsi-jvb }
spec:
nodeSelector:
kubernetes.io/hostname: titan-22
kubernetes.io/arch: amd64
containers:
- name: jvb
image: jitsi/jvb:stable
ports:
- { name: colibri-ws, containerPort: 9090, protocol: TCP } # WebSocket control channel
- { name: rtp-udp, containerPort: 10000, hostPort: 10000, protocol: UDP } # media
env:
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
- { name: XMPP_SERVER, value: "jitsi-prosody.jitsi.svc.cluster.local" }
- { name: JVB_AUTH_USER, value: "jvb" }
- name: JVB_AUTH_PASSWORD
valueFrom: { secretKeyRef: { name: jitsi-internal-secrets, key: JVB_AUTH_PASSWORD } }
- { name: JVB_BREWERY_MUC, value: "jvbbrewery" }
- { name: JVB_PORT, value: "10000" } # matches hostPort above
- { name: ENABLE_COLIBRI_WEBSOCKET, value: "1" } # enables /colibri-ws
- { name: JVB_STUN_SERVERS, value: "stun.l.google.com:19302,stun1.l.google.com:19302,meet-jit-si-turnrelay.jitsi.net:443" }
# For pure speed you can advertise your public IP here if STUN is flaky:
# - name: JVB_ADVERTISE_IPS
# value: "YOUR.PUBLIC.IP.ADDR"
volumeMounts:
- { name: cfg, mountPath: /config }
volumes:
- name: cfg
persistentVolumeClaim: { claimName: jitsi-jvb-config }
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jitsi-web
namespace: jitsi
spec:
replicas: 1
selector:
matchLabels: { app: jitsi-web }
template:
metadata:
labels: { app: jitsi-web }
spec:
nodeSelector:
kubernetes.io/hostname: titan-22
kubernetes.io/arch: amd64
containers:
- name: web
image: jitsi/web:stable
ports:
- { name: http, containerPort: 80, protocol: TCP }
env:
- { name: PUBLIC_URL, value: "https://meet.bstein.dev" }
- { name: XMPP_DOMAIN, value: "meet.jitsi" }
- { name: XMPP_AUTH_DOMAIN, value: "auth.meet.jitsi" }
- { name: XMPP_MUC_DOMAIN, value: "muc.meet.jitsi" }
- { name: XMPP_INTERNAL_MUC_DOMAIN, value: "internal-muc.meet.jitsi" }
- { name: XMPP_BOSH_URL_BASE, value: "http://jitsi-prosody.jitsi.svc.cluster.local:5280" }
- { name: ENABLE_XMPP_WEBSOCKET, value: "1" }
- { name: ENABLE_COLIBRI_WEBSOCKET, value: "1" }
# Optional: lower default quality a bit for tough links
# - { name: RESOLUTION, value: "360" }
volumeMounts:
- { name: cfg, mountPath: /config }
volumes:
- name: cfg
persistentVolumeClaim: { claimName: jitsi-web-config }

View File

@ -0,0 +1,41 @@
# services/jitsi/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jitsi
namespace: jitsi
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: traefik
tls:
- hosts: [ "meet.bstein.dev" ]
secretName: jitsi-meet-tls
rules:
- host: meet.bstein.dev
http:
paths:
- path: /colibri-ws
pathType: Prefix
backend:
service:
name: jitsi-jvb
port: { number: 9090 }
- path: /xmpp-websocket
pathType: Prefix
backend:
service:
name: jitsi-prosody
port: { number: 5280 }
- path: /http-bind
pathType: Prefix
backend:
service:
name: jitsi-prosody
port: { number: 5280 }
- path: /
pathType: Prefix
backend:
service:
name: jitsi-web
port: { number: 80 }

View File

@ -0,0 +1,10 @@
# services/jitsi/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
- pvc.yaml
- ingress.yaml
- secret.yaml

View File

@ -0,0 +1,5 @@
# services/jitsi/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: jitsi

42
services/jitsi/pvc.yaml Normal file
View File

@ -0,0 +1,42 @@
# services/jitsi/pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jitsi-web-config
namespace: jitsi
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jitsi-prosody-config
namespace: jitsi
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jitsi-jicofo-config
namespace: jitsi
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jitsi-jvb-config
namespace: jitsi
spec:
accessModes: ["ReadWriteOnce"]
resources: { requests: { storage: 10Gi } }

View File

@ -0,0 +1,11 @@
# services/jitsi/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: jitsi-internal-secrets
namespace: jitsi
type: Opaque
data:
JICOFO_COMPONENT_SECRET: bEg5Y09hZFJBem5PUFliQlp4RHkwRTRP
JICOFO_AUTH_PASSWORD: VVkyUmczaVRDWUZ0MzdQdmN3UDN1SFc5
JVB_AUTH_PASSWORD: d0M5aWJ4dWlPTnhFak9lRHJqSHdYa0g5

View File

@ -0,0 +1,36 @@
# services/jitsi/service.yaml
apiVersion: v1
kind: Service
metadata:
name: jitsi-prosody
namespace: jitsi
spec:
selector: { app: jitsi-prosody }
ports:
- { name: c2s, port: 5222, targetPort: 5222, protocol: TCP }
- { name: http, port: 5280, targetPort: 5280, protocol: TCP }
- { name: comp, port: 5347, targetPort: 5347, protocol: TCP }
---
apiVersion: v1
kind: Service
metadata:
name: jitsi-jvb
namespace: jitsi
spec:
selector: { app: jitsi-jvb }
ports:
- { name: colibri-ws, port: 9090, targetPort: 9090, protocol: TCP }
---
apiVersion: v1
kind: Service
metadata:
name: jitsi-web
namespace: jitsi
spec:
selector: { app: jitsi-web }
ports:
- { name: http, port: 80, targetPort: 80, protocol: TCP }

View File

@ -0,0 +1,25 @@
# services/pegasus/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: pegasus-config
namespace: jellyfin
data:
PEGASUS_MEDIA_ROOT: "/media"
PEGASUS_BIND: ":8080"
# Optional: PEGASUS_COOKIE_DOMAIN
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pegasus-user-map
namespace: jellyfin
data:
user-map.yaml: |
map:
mary_grace_allison: "Allison"
maddie_rejcek: "Rejcek"
carol_mcguinness: "McGuinness_90s"
ed_stein: "Stein_90s"

View File

@ -0,0 +1,63 @@
# services/pegasus/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pegasus
namespace: jellyfin
spec:
replicas: 1
selector: { matchLabels: { app: pegasus } }
template:
metadata: { labels: { app: pegasus } }
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 1000
fsGroupChangePolicy: "OnRootMismatch"
containers:
- name: pegasus
image: registry.bstein.dev/pegasus:1.1.0
imagePullPolicy: IfNotPresent
env:
- name: PEGASUS_MEDIA_ROOT
valueFrom: { configMapKeyRef: { name: pegasus-config, key: PEGASUS_MEDIA_ROOT } }
- name: PEGASUS_BIND
valueFrom: { configMapKeyRef: { name: pegasus-config, key: PEGASUS_BIND } }
- name: PEGASUS_USER_MAP_FILE
value: "/config/user-map.yaml"
- name: PEGASUS_SESSION_KEY
valueFrom: { secretKeyRef: { name: pegasus-secrets, key: PEGASUS_SESSION_KEY } }
- name: JELLYFIN_URL
valueFrom: { secretKeyRef: { name: pegasus-secrets, key: JELLYFIN_URL } }
- name: PEGASUS_DEBUG
value: 1
- name: PEGASUS_DRY_RUN
value: 1
ports: [{ name: http, containerPort: 8080 }]
volumeMounts:
- name: media
mountPath: /media
- name: config
mountPath: /config
readOnly: true
- name: tmp
mountPath: /tmp
readinessProbe: { httpGet: { path: "/", port: http } }
livenessProbe: { httpGet: { path: "/metrics", port: http } }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 1000m, memory: 1Gi }
volumes:
- name: media
persistentVolumeClaim:
claimName: jellyfin-media-asteria
- name: config
configMap: { name: pegasus-user-map }
- name: tmp
emptyDir: {}

View File

@ -0,0 +1,24 @@
# services/pegasus/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pegasus
namespace: jellyfin
annotations:
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- hosts: [ "pegasus.bstein.dev" ]
rules:
- host: pegasus.bstein.dev
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: pegasus
port: { number: 80 }

View File

@ -0,0 +1,27 @@
# services/pegasus/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- configmap.yaml
- service.yaml
- deployment.yaml
- ingress.yaml
patches:
- target: { kind: Deployment, name: pegasus, namespace: jellyfin }
patch: |
- op: add
path: /spec/template/spec/containers/0/env/-
value: { name: PEGASUS_DEBUG, value: "1" }
- op: add
path: /spec/template/spec/containers/0/env/-
value: { name: PEGASUS_DRY_RUN, value: "1" }
- op: add
path: /spec/template/spec/containers/-
value:
name: shell
image: alpine:3.20
command: ["sh","-c","sleep infinity"]
securityContext: { runAsUser: 10001, runAsGroup: 10001, allowPrivilegeEscalation: false }
volumeMounts:
- { name: media, mountPath: /media }
- { name: config, mountPath: /config, readOnly: true }

View File

@ -0,0 +1,12 @@
# services/pegasus/service.yaml
apiVersion: v1
kind: Service
metadata:
name: pegasus
namespace: jellyfin
spec:
selector: { app: pegasus }
ports:
- name: http
port: 80
targetPort: http