diff --git a/-c b/-c deleted file mode 100644 index e69de29..0000000 diff --git a/infrastructure/flux-system/kustomization-jitsi.yaml b/infrastructure/flux-system/kustomization-jitsi.yaml new file mode 100644 index 0000000..66279a9 --- /dev/null +++ b/infrastructure/flux-system/kustomization-jitsi.yaml @@ -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 diff --git a/infrastructure/flux-system/kustomization-longhorn-ui.yaml b/infrastructure/flux-system/kustomization-longhorn-ui.yaml index f96944b..e897cf7 100644 --- a/infrastructure/flux-system/kustomization-longhorn-ui.yaml +++ b/infrastructure/flux-system/kustomization-longhorn-ui.yaml @@ -1,3 +1,4 @@ +# infrastructure/flux-system/kustomization-longhorn-ui.yaml apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: diff --git a/infrastructure/flux-system/kustomization.yaml b/infrastructure/flux-system/kustomization.yaml index 4ef0243..8826ae4 100644 --- a/infrastructure/flux-system/kustomization.yaml +++ b/infrastructure/flux-system/kustomization.yaml @@ -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 diff --git a/services/jitsi/deployment.yaml b/services/jitsi/deployment.yaml new file mode 100644 index 0000000..0cd247f --- /dev/null +++ b/services/jitsi/deployment.yaml @@ -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 } diff --git a/services/jitsi/ingress.yaml b/services/jitsi/ingress.yaml new file mode 100644 index 0000000..c09b669 --- /dev/null +++ b/services/jitsi/ingress.yaml @@ -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 } diff --git a/services/jitsi/kustomization.yaml b/services/jitsi/kustomization.yaml new file mode 100644 index 0000000..8864598 --- /dev/null +++ b/services/jitsi/kustomization.yaml @@ -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 diff --git a/services/jitsi/namespace.yaml b/services/jitsi/namespace.yaml new file mode 100644 index 0000000..6ba93f2 --- /dev/null +++ b/services/jitsi/namespace.yaml @@ -0,0 +1,5 @@ +# services/jitsi/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: jitsi diff --git a/services/jitsi/pvc.yaml b/services/jitsi/pvc.yaml new file mode 100644 index 0000000..3a2c14e --- /dev/null +++ b/services/jitsi/pvc.yaml @@ -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 } } diff --git a/services/jitsi/secret.yaml b/services/jitsi/secret.yaml new file mode 100644 index 0000000..f851bac --- /dev/null +++ b/services/jitsi/secret.yaml @@ -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 \ No newline at end of file diff --git a/services/jitsi/service.yaml b/services/jitsi/service.yaml new file mode 100644 index 0000000..7b44b5c --- /dev/null +++ b/services/jitsi/service.yaml @@ -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 } diff --git a/services/pegasus/configmap.yaml b/services/pegasus/configmap.yaml new file mode 100644 index 0000000..69b1751 --- /dev/null +++ b/services/pegasus/configmap.yaml @@ -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" \ No newline at end of file diff --git a/services/pegasus/deployment.yaml b/services/pegasus/deployment.yaml new file mode 100644 index 0000000..5de2039 --- /dev/null +++ b/services/pegasus/deployment.yaml @@ -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: {} \ No newline at end of file diff --git a/services/pegasus/ingress.yaml b/services/pegasus/ingress.yaml new file mode 100644 index 0000000..dfdbf56 --- /dev/null +++ b/services/pegasus/ingress.yaml @@ -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 } diff --git a/services/pegasus/kustomization.yaml b/services/pegasus/kustomization.yaml new file mode 100644 index 0000000..ee286ce --- /dev/null +++ b/services/pegasus/kustomization.yaml @@ -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 } diff --git a/services/pegasus/service.yaml b/services/pegasus/service.yaml new file mode 100644 index 0000000..83581bb --- /dev/null +++ b/services/pegasus/service.yaml @@ -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