From cf572c167f8df46f3aac715f144311a4c7b9997e Mon Sep 17 00:00:00 2001 From: Andrew Stoltz Date: Fri, 10 Apr 2026 22:49:48 -0500 Subject: [PATCH] Update Guacamole: branding JAR, K8s sync CronJob - Updated bluejay-branding-1.0.0.jar with gold accents, hover fix, icon fix, pinstripe patterns, Blue Jay SVG logo - Added guac-k8s-sync CronJob: runs every 2min, auto-updates pod names in Kubernetes exec connections when pods restart - Fixed secret reference (guacamole-credentials, not guacamole-db-credentials) Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/guacamole/guacamole.yaml | 1022 ++++++++++++++++++--------------- 1 file changed, 565 insertions(+), 457 deletions(-) diff --git a/apps/guacamole/guacamole.yaml b/apps/guacamole/guacamole.yaml index 5e13f7c..94728ed 100644 --- a/apps/guacamole/guacamole.yaml +++ b/apps/guacamole/guacamole.yaml @@ -1,457 +1,565 @@ -# 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: - serviceAccountName: guacd-exec - containers: - - name: guacd - image: guacamole/guacd:latest - ports: - - containerPort: 4822 - name: guacd - env: - - name: LOG_LEVEL - value: debug - resources: - requests: - memory: 128Mi - cpu: 100m - limits: - memory: 512Mi - cpu: 500m - livenessProbe: - tcpSocket: - port: 4822 - initialDelaySeconds: 15 - periodSeconds: 10 - - name: kubectl-proxy - image: bitnami/kubectl:latest - args: - - proxy - - "--port=8001" - - "--address=127.0.0.1" - - "--accept-hosts=.*" - - "--accept-paths=.*" - - "--disable-filter=true" - - "--v=2" - resources: - requests: - memory: 32Mi - cpu: 10m - limits: - memory: 64Mi - cpu: 50m ---- -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 - volumeMounts: - - name: guac-properties - mountPath: /etc/guacamole/guacamole.properties - subPath: guacamole.properties - - name: bluejay-branding - mountPath: /etc/guacamole/extensions/bluejay-branding-1.0.0.jar - subPath: bluejay-branding-1.0.0.jar - volumes: - - name: guac-properties - configMap: - name: guacamole-properties - - name: bluejay-branding - configMap: - name: guacamole-branding ---- -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 ---- -# Blue Jay Branding Extension (CSS + translations) -apiVersion: v1 -kind: ConfigMap -metadata: - name: guacamole-branding - namespace: guacamole -binaryData: - bluejay-branding-1.0.0.jar: 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 ---- -# Guacamole custom properties -apiVersion: v1 -kind: ConfigMap -metadata: - name: guacamole-properties - namespace: guacamole -data: - guacamole.properties: | - # Blue Jay Remote Access — Guacamole Configuration - # MySQL/guacd settings provided via env vars — do NOT duplicate here - - # Extension Priority - extension-priority: mysql, ban, bluejay, * - - # 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 - api-session-timeout: 60 ---- -# guacd ServiceAccount for K8s exec -apiVersion: v1 -kind: ServiceAccount -metadata: - name: guacd-exec - namespace: guacamole ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: guacd-pod-exec - labels: - app.kubernetes.io/component: proxy - app.kubernetes.io/name: guacd -rules: - - apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list"] - - apiGroups: [""] - resources: ["pods/exec", "pods/attach"] - verbs: ["create", "get"] - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["list", "get"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: guacd-pod-exec - labels: - app.kubernetes.io/component: proxy - app.kubernetes.io/name: guacd -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: guacd-pod-exec -subjects: - - kind: ServiceAccount - name: guacd-exec - namespace: guacamole +# 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: + serviceAccountName: guacd-exec + containers: + - name: guacd + image: guacamole/guacd:latest + ports: + - containerPort: 4822 + name: guacd + env: + - name: LOG_LEVEL + value: debug + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m + livenessProbe: + tcpSocket: + port: 4822 + initialDelaySeconds: 15 + periodSeconds: 10 + - name: kubectl-proxy + image: bitnami/kubectl:latest + args: + - proxy + - "--port=8001" + - "--address=127.0.0.1" + - "--accept-hosts=.*" + - "--accept-paths=.*" + - "--disable-filter=true" + - "--v=2" + resources: + requests: + memory: 32Mi + cpu: 10m + limits: + memory: 64Mi + cpu: 50m +--- +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 + volumeMounts: + - name: guac-properties + mountPath: /etc/guacamole/guacamole.properties + subPath: guacamole.properties + - name: bluejay-branding + mountPath: /etc/guacamole/extensions/bluejay-branding-1.0.0.jar + subPath: bluejay-branding-1.0.0.jar + volumes: + - name: guac-properties + configMap: + name: guacamole-properties + - name: bluejay-branding + configMap: + name: guacamole-branding +--- +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 +--- +# Blue Jay Branding Extension (CSS + translations) +apiVersion: v1 +kind: ConfigMap +metadata: + name: guacamole-branding + namespace: guacamole +binaryData: + bluejay-branding-1.0.0.jar: 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 +--- +# Guacamole custom properties +apiVersion: v1 +kind: ConfigMap +metadata: + name: guacamole-properties + namespace: guacamole +data: + guacamole.properties: | + # Blue Jay Remote Access — Guacamole Configuration + # MySQL/guacd settings provided via env vars — do NOT duplicate here + + # Extension Priority + extension-priority: mysql, ban, bluejay, * + + # 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 + api-session-timeout: 60 +--- +# guacd ServiceAccount for K8s exec +apiVersion: v1 +kind: ServiceAccount +metadata: + name: guacd-exec + namespace: guacamole +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: guacd-pod-exec + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/name: guacd +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list"] + - apiGroups: [""] + resources: ["pods/exec", "pods/attach"] + verbs: ["create", "get"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["list", "get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: guacd-pod-exec + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/name: guacd +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: guacd-pod-exec +subjects: + - kind: ServiceAccount + name: guacd-exec + namespace: guacamole +--- +# K8s pod sync CronJob — auto-updates Guacamole connections when pods restart +apiVersion: batch/v1 +kind: CronJob +metadata: + name: guac-k8s-sync + namespace: guacamole + labels: + app.kubernetes.io/name: guac-k8s-sync + app.kubernetes.io/component: sync +spec: + schedule: "*/2 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + spec: + serviceAccountName: guacd-exec + restartPolicy: OnFailure + containers: + - name: sync + image: bitnami/kubectl:latest + command: + - /bin/bash + - /scripts/sync-k8s-connections.sh + env: + - name: GUAC_URL + value: "http://guacamole.guacamole.svc.cluster.local:8080" + - name: GUAC_ADMIN_USER + value: "guacadmin" + - name: GUAC_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: guacamole-credentials + key: password + - name: TARGET_NAMESPACES + value: "argocd,gitea,telephony,traefik-system,zabbix,matrix,irc,mail,selenium" + volumeMounts: + - name: scripts + mountPath: /scripts + volumes: + - name: scripts + configMap: + name: guac-k8s-sync-scripts + defaultMode: 0755 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: guac-k8s-sync-scripts + namespace: guacamole + labels: + app.kubernetes.io/name: guac-k8s-sync + app.kubernetes.io/component: sync +data: + sync-k8s-connections.sh: | + #!/bin/bash + set -euo pipefail + GUAC_API="${GUAC_URL}/guacamole/api" + DATASOURCE="mysql" + echo "[k8s-sync] Starting pod connection sync" + TOKEN=$(curl -sf -d "username=${GUAC_ADMIN_USER}&password=${GUAC_ADMIN_PASSWORD}" \ + "${GUAC_API}/tokens" | python3 -c "import sys,json; print(json.load(sys.stdin)['authToken'])") + if [ -z "$TOKEN" ]; then echo "[k8s-sync] ERROR: Auth failed"; exit 1; fi + # Find Kubernetes group + K8S_GROUP_ID=$(curl -sf "${GUAC_API}/session/data/${DATASOURCE}/connectionGroups/ROOT/tree?token=${TOKEN}" \ + | python3 -c " + import sys, json + tree = json.load(sys.stdin) + for g in tree.get('childConnectionGroups', []): + if g.get('name') == 'Kubernetes': + print(g['identifier']); sys.exit(0) + print('')") + if [ -z "$K8S_GROUP_ID" ]; then echo "[k8s-sync] No Kubernetes group"; exit 0; fi + # Get existing connections + EXISTING=$(curl -sf "${GUAC_API}/session/data/${DATASOURCE}/connections?token=${TOKEN}" \ + | python3 -c " + import sys, json + conns = json.load(sys.stdin) + for k,v in conns.items(): + if v.get('protocol') == 'kubernetes': + params = v.get('parameters', {}) + print(f'{k}\t{params.get(\"namespace\",\"\")}\t{params.get(\"pod\",\"\")}')" 2>/dev/null || echo "") + IFS=',' read -ra NAMESPACES <<< "$TARGET_NAMESPACES" + UPDATED=0 + for NS in "${NAMESPACES[@]}"; do + PODS=$(kubectl get pods -n "$NS" --field-selector=status.phase=Running \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].name}{"\n"}{end}' 2>/dev/null || echo "") + while IFS=$'\t' read -r POD CONTAINER; do + [ -z "$POD" ] && continue + # Check if a connection exists for this namespace+container combo + CONN_ID=$(echo "$EXISTING" | grep " ${NS} " | head -1 | cut -f1) + if [ -n "$CONN_ID" ]; then + # Update existing connection pod name + OLD_POD=$(echo "$EXISTING" | grep "^${CONN_ID} " | cut -f3) + if [ "$OLD_POD" != "$POD" ]; then + echo "[k8s-sync] Updating ${NS}/${CONTAINER}: ${OLD_POD} -> ${POD}" + curl -sf -X PUT -H "Content-Type: application/json" \ + -d "{\"name\":\"k8s: ${CONTAINER}\",\"parentIdentifier\":\"ROOT\",\"protocol\":\"kubernetes\",\"parameters\":{\"hostname\":\"localhost\",\"port\":\"8001\",\"namespace\":\"${NS}\",\"pod\":\"${POD}\",\"container\":\"${CONTAINER}\",\"use-ssl\":\"false\",\"exec-command\":\"/bin/sh\",\"font-size\":\"14\",\"color-scheme\":\"gray-black\",\"scrollback-size\":\"5000\"},\"attributes\":{\"max-connections\":\"2\",\"max-connections-per-user\":\"1\"}}" \ + "${GUAC_API}/session/data/${DATASOURCE}/connections/${CONN_ID}?token=${TOKEN}" > /dev/null + UPDATED=$((UPDATED + 1)) + fi + fi + done <<< "$PODS" + done + echo "[k8s-sync] Done: ${UPDATED} updated"