From 71b3cef2eb8d364eed36825c79f36e48756e07f9 Mon Sep 17 00:00:00 2001 From: rock Date: Tue, 6 Oct 2026 10:10:56 +0800 Subject: [PATCH 1/3] fix(minio): replace the dead minio/mc image in the provision hook and backup job --- k8s/apps/paperless/backup-cronjob.yaml | 3 ++- k8s/infra/minio/minio-provision-paperless-job.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/k8s/apps/paperless/backup-cronjob.yaml b/k8s/apps/paperless/backup-cronjob.yaml index 46dcbd6..9035af8 100644 --- a/k8s/apps/paperless/backup-cronjob.yaml +++ b/k8s/apps/paperless/backup-cronjob.yaml @@ -48,7 +48,8 @@ spec: mountPath: /backup containers: - name: mc-mirror - image: quay.io/minio/mc:latest + # bitnamilegacy/minio-client:2025.7.21-debian-12-r3 replaces the dead quay.io/minio/mc image + image: bitnamilegacy/minio-client:2025.7.21-debian-12-r3 env: - name: ACCESS_KEY valueFrom: diff --git a/k8s/infra/minio/minio-provision-paperless-job.yaml b/k8s/infra/minio/minio-provision-paperless-job.yaml index d1e849e..c01ad57 100644 --- a/k8s/infra/minio/minio-provision-paperless-job.yaml +++ b/k8s/infra/minio/minio-provision-paperless-job.yaml @@ -59,7 +59,8 @@ spec: mountPath: /shared containers: - name: provision - image: quay.io/minio/mc:latest + # bitnamilegacy/minio-client:2025.7.21-debian-12-r3 replaces the dead quay.io/minio/mc image + image: bitnamilegacy/minio-client:2025.7.21-debian-12-r3 volumeMounts: - name: shared mountPath: /shared -- 2.54.0 From ebb1b9b02decc07f84a916c101bf2aa4de121ea6 Mon Sep 17 00:00:00 2001 From: rock Date: Tue, 6 Oct 2026 12:45:12 +0800 Subject: [PATCH 2/3] feat(storage): add local-path-provisioner as the fleet's persistent-storage backend --- k8s/argocd/apps/10-storage-observability.yaml | 23 ++ k8s/infra/local-path/kustomization.yaml | 7 + k8s/infra/local-path/local-path-storage.yaml | 200 ++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 k8s/infra/local-path/kustomization.yaml create mode 100644 k8s/infra/local-path/local-path-storage.yaml diff --git a/k8s/argocd/apps/10-storage-observability.yaml b/k8s/argocd/apps/10-storage-observability.yaml index 1b57f05..f4e0823 100644 --- a/k8s/argocd/apps/10-storage-observability.yaml +++ b/k8s/argocd/apps/10-storage-observability.yaml @@ -249,3 +249,26 @@ spec: selfHeal: true syncOptions: - CreateNamespace=true +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: local-path-provisioner + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" +spec: + project: homelab + source: + repoURL: https://forgejo.riotpiao.com/riotpiao-poimen/homelab.git + targetRevision: main + path: k8s/infra/local-path + destination: + server: https://kubernetes.default.svc + namespace: local-path-storage + # No CreateNamespace: the Namespace object (with its privileged PSA label, + # which the helper Pods need for their hostPath) is part of the manifest. + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/k8s/infra/local-path/kustomization.yaml b/k8s/infra/local-path/kustomization.yaml new file mode 100644 index 0000000..e315384 --- /dev/null +++ b/k8s/infra/local-path/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +# No commonLabels on purpose: this is a vendored third-party manifest and +# kustomize would also rewrite the provisioner Deployment's selector, which +# is immutable on re-apply. +resources: + - local-path-storage.yaml diff --git a/k8s/infra/local-path/local-path-storage.yaml b/k8s/infra/local-path/local-path-storage.yaml new file mode 100644 index 0000000..111feb7 --- /dev/null +++ b/k8s/infra/local-path/local-path-storage.yaml @@ -0,0 +1,200 @@ +# Vendored from https://github.com/rancher/local-path-provisioner +# deploy/local-path-storage.yaml @ v0.0.37 (release 2026-08-05) +# +# Local modifications (the only three; re-apply them on any re-vendor): +# 1. the namespace carries pod-security.kubernetes.io/enforce=privileged, because the +# provisioner's helper Pods mount a hostPath and this cluster defaults to the +# baseline policy, which forbids that; +# 2. config.json nodePathMap is /var/lib/local-path-provisioner — Talos mounts the root +# filesystem read-only, so upstream's /opt path cannot be created; +# 3. the helper Pod's busybox image is pinned to a tag instead of a floating name. +apiVersion: v1 +kind: Namespace +metadata: + name: local-path-storage + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: local-path-provisioner-service-account + namespace: local-path-storage + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: local-path-provisioner-role + namespace: local-path-storage +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: local-path-provisioner-role +rules: + - apiGroups: [""] + resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: local-path-provisioner-bind + namespace: local-path-storage +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: local-path-provisioner-role +subjects: + - kind: ServiceAccount + name: local-path-provisioner-service-account + namespace: local-path-storage + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: local-path-provisioner-bind +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: local-path-provisioner-role +subjects: + - kind: ServiceAccount + name: local-path-provisioner-service-account + namespace: local-path-storage + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: local-path-provisioner + namespace: local-path-storage +spec: + replicas: 1 + selector: + matchLabels: + app: local-path-provisioner + template: + metadata: + labels: + app: local-path-provisioner + spec: + serviceAccountName: local-path-provisioner-service-account + containers: + - name: local-path-provisioner + image: docker.io/rancher/local-path-provisioner:v0.0.37 + imagePullPolicy: IfNotPresent + command: + - local-path-provisioner + - --debug + - start + - --config + - /etc/config/config.json + ports: + - name: health + containerPort: 8080 + protocol: TCP + startupProbe: + httpGet: + path: /health + port: health + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 12 + livenessProbe: + httpGet: + path: /health + port: health + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /ready + port: health + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: config-volume + mountPath: /etc/config/ + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CONFIG_MOUNT_PATH + value: /etc/config/ + - name: HEALTH_PORT + value: "8080" + volumes: + - name: config-volume + configMap: + name: local-path-config + +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: local-path +provisioner: rancher.io/local-path +volumeBindingMode: WaitForFirstConsumer +reclaimPolicy: Delete + +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: local-path-config + namespace: local-path-storage +data: + config.json: |- + { + "nodePathMap":[ + { + "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", + "paths":["/var/lib/local-path-provisioner"] + } + ] + } + setup: |- + #!/bin/sh + set -eu + mkdir -m 0777 -p "$VOL_DIR" + teardown: |- + #!/bin/sh + set -eu + rm -rf "$VOL_DIR" + helperPod.yaml: |- + apiVersion: v1 + kind: Pod + metadata: + name: helper-pod + spec: + priorityClassName: system-node-critical + tolerations: + - key: node.kubernetes.io/disk-pressure + operator: Exists + effect: NoSchedule + containers: + - name: helper-pod + image: docker.io/library/busybox:1.36 + imagePullPolicy: IfNotPresent -- 2.54.0 From 227a67a8c117ed1ce63fde6eccc8cb38056ee0f0 Mon Sep 17 00:00:00 2001 From: rock Date: Sun, 25 Oct 2026 19:57:37 -0700 Subject: [PATCH 3/3] feat(cloudflared): manage tunnel ingress locally via ConfigMap The tunnel was remotely managed from the Zero Trust dashboard: the Deployment ran 'run --token' and ignored the ConfigMap that already existed in the tree. Switch to a locally-managed tunnel so the ingress routing table lives in git. - deployment: mount cloudflared-config (config.yml) and the cloudflared-credentials Secret; drop TUNNEL_TOKEN env - configmap.yaml: per-hostname ingress rules to ingress-nginx via the CoreDNS rewrite, plus a 404 catch-all - secret-generator: register the SOPS-encrypted credentials file - homelab-ca: add the cloudflared namespace copy so the connector trusts the ingress certificate (force-ssl-redirect makes plaintext impossible) Credentials were extracted from the live cloudflared-token Secret and re-encrypted with SOPS; no plaintext secret is committed. --- k8s/apps/cloudflared/configmap.yaml | 58 +++++++++++++++++++ k8s/apps/cloudflared/deployment.yaml | 56 +++++++++++++++--- k8s/apps/cloudflared/kustomization.yaml | 1 + .../secrets/cloudflared-credentials.enc.yaml | 23 ++++++++ k8s/argocd/secrets/secret-generator.yaml | 1 + .../cert-manager/homelab-ca-configmap.yaml | 37 ++++++++++++ 6 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 k8s/apps/cloudflared/configmap.yaml create mode 100644 k8s/argocd/secrets/cloudflared-credentials.enc.yaml diff --git a/k8s/apps/cloudflared/configmap.yaml b/k8s/apps/cloudflared/configmap.yaml new file mode 100644 index 0000000..48bdccb --- /dev/null +++ b/k8s/apps/cloudflared/configmap.yaml @@ -0,0 +1,58 @@ +# cloudflared ingress configuration. +# +# This list is the tunnel's routing table: for each incoming hostname the +# connector dials the matching origin. The final rule is a catch-all, so a +# hostname absent from this list gets 404 from the connector — DNS alone does +# not route a request into a service. +# +# Management model: this file makes the tunnel LOCALLY managed. cloudflared is +# given a credentials file instead of a token, so Cloudflare stops pushing the +# remotely-managed ingess list and this ConfigMap wins. Revert the Deployment to +# `run --token` to return to the dashboard-managed list. +# +# Origin services self-reference the public hostname. That resolves because +# CoreDNS rewrites each of these names to ingress-nginx +# (terraform/files/coredns/Corefile), so the connector reaches the in-cluster +# ingress instead of looping back out through Cloudflare. +apiVersion: v1 +kind: ConfigMap +metadata: + name: cloudflared-config + namespace: cloudflared +data: + config.yml: | + tunnel: 32d0bf93-e1bd-45b8-93a9-71ff9f83de80 + credentials-file: /etc/cloudflared/creds/credentials.json + + # WARP-to-Tunnel routing. The remotely-managed config had this enabled; + # dropping the key would break WARP access to private addresses. cloudflared + # 2026.x has no `enabled` field — the key's presence is what enables it, and + # `enabled: true` here makes the binary warn about an unused key. + warp-routing: {} + + ingress: + - hostname: paperless.riotpiao.com + service: https://paperless.riotpiao.com + - hostname: authentik.riotpiao.com + service: https://authentik.riotpiao.com + - hostname: img.riotpiao.com + service: https://img.riotpiao.com + - hostname: riotpiao.com + service: https://riotpiao.com + - hostname: grafana.riotpiao.com + service: https://grafana.riotpiao.com + - hostname: gotify.riotpiao.com + service: https://gotify.riotpiao.com + - hostname: igotify.riotpiao.com + service: https://igotify.riotpiao.com + - hostname: poimen.riotpiao.com + service: https://poimen.riotpiao.com + - hostname: comfyui.riotpiao.com + service: https://comfyui.riotpiao.com + - hostname: forgejo.riotpiao.com + service: https://forgejo.riotpiao.com + # ZITADEL. Without this rule the connector answers 404 for the hostname + # even though the DNS CNAME points at the tunnel. + - hostname: zitadel.riotpiao.com + service: https://zitadel.riotpiao.com + - service: http_status:404 diff --git a/k8s/apps/cloudflared/deployment.yaml b/k8s/apps/cloudflared/deployment.yaml index 178f801..8087b62 100644 --- a/k8s/apps/cloudflared/deployment.yaml +++ b/k8s/apps/cloudflared/deployment.yaml @@ -13,23 +13,63 @@ spec: labels: app: cloudflared spec: + # Ingress-nginx forces HTTPS (force-ssl-redirect), so cloudflared must + # dial https:// and trust homelab-ca or the TLS handshake fails + # with "certificate signed by unknown authority". Same pattern as + # authentik-values.yaml: merge the system bundle with homelab-ca.crt. + initContainers: + - name: merge-ca-certs + image: debian:bookworm + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 0 + command: + - sh + - -c + - (cat /etc/ssl/certs/ca-certificates.crt 2>/dev/null; cat /homelab-ca/homelab-ca.crt) > /merged/ca-bundle.crt + volumeMounts: + - name: homelab-ca + mountPath: /homelab-ca + readOnly: true + - name: merged-ca + mountPath: /merged + volumes: + - name: config + configMap: + name: cloudflared-config + - name: credentials + secret: + secretName: cloudflared-credentials + - name: homelab-ca + configMap: + name: homelab-ca + - name: merged-ca + emptyDir: {} containers: - name: cloudflared image: cloudflare/cloudflared:latest args: - tunnel - --no-autoupdate - - run - --protocol - http2 - - --token - - $(TUNNEL_TOKEN) + - --config + - /etc/cloudflared/config.yml + - run env: - - name: TUNNEL_TOKEN - valueFrom: - secretKeyRef: - name: cloudflared-token - key: token + - name: SSL_CERT_FILE + value: /merged/ca-bundle.crt + volumeMounts: + - name: config + mountPath: /etc/cloudflared/config.yml + subPath: config.yml + readOnly: true + - name: credentials + mountPath: /etc/cloudflared/creds + readOnly: true + - name: merged-ca + mountPath: /merged + readOnly: true resources: requests: cpu: 10m diff --git a/k8s/apps/cloudflared/kustomization.yaml b/k8s/apps/cloudflared/kustomization.yaml index 3e60949..93f4bb6 100644 --- a/k8s/apps/cloudflared/kustomization.yaml +++ b/k8s/apps/cloudflared/kustomization.yaml @@ -3,3 +3,4 @@ kind: Kustomization namespace: cloudflared resources: - deployment.yaml + - configmap.yaml diff --git a/k8s/argocd/secrets/cloudflared-credentials.enc.yaml b/k8s/argocd/secrets/cloudflared-credentials.enc.yaml new file mode 100644 index 0000000..d3e1028 --- /dev/null +++ b/k8s/argocd/secrets/cloudflared-credentials.enc.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cloudflared-credentials + namespace: cloudflared +type: Opaque +stringData: + credentials.json: ENC[AES256_GCM,data:LnJVGuxJAOwDYbLgX00OHICDDldZgwgvGafOIwKoJKnjg3FjFMQYThNCOL/H+6tctuKixJNcAckjg70tmCKWxNq9I35XX/YSqob6Qv4fywv32hsNIAe44BU035MGZ5zmc6bZZ8wAgTJZw6Th4TzY7taFBCRdj2fW4T+qVfq7strn68G4ufVMK6UXlOkEDLjR2Vxj32lqSLdbdMma96XB+265hwL6IVfaugG662mcGhPwgHk=,iv:NFmyHN5m2mJUBgn1qgmJZ2YgkFxBZyp7+gU1Ng6B/7M=,tag:/66JtHK5Yu7pRxB0b0eifQ==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsaDVycUE4RFpoRjdpTlpX + dGpzN3hwRFQ2cmtXVXVzWnZJOXJEZXZRWEhNCkJwbm9QV0lGcGtNRTJqbStrc3l1 + Q2M4bE1WbzJpTXZQWjJyTGpFS0JYcVUKLS0tIDBvU2U5aEtVTTZUaE85eEkyYitG + bGtSVkhKNnRuNlZWMWh6Z1M4VXM1WU0KdusvtM85V0xrO+uJFUY+UH0tuwdF0GiW + 79J4Rlxk8ne25psKsvwjLYLxZ53xjFHCC4FCHTNkYwTKCDEKjw46Lg== + -----END AGE ENCRYPTED FILE----- + recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-10-26T02:45:04Z" + mac: ENC[AES256_GCM,data:KF7KwXCnNLAg1YI3u0lLI/ho4sITHAq09ac2WO4TK3TCthkuivb89YjhuuJKs0NFhjn6aRLqmZavUqcXH2HujiryqbLNVexOShQM0wSzgCzmWdbi0UzpgJ4KcQA3PufDtsQz4HHLXx396EbZrM3Ie3WSu/PvRm5iWOxzTGCGc9Q=,iv:Eo920oXXD1FLZVJXywZ6C+LRL+qZjmnZXv0+Oa/GMb8=,tag:6LuPVSet4WjvrvGUAxaJlw==,type:str] + version: 3.13.2 diff --git a/k8s/argocd/secrets/secret-generator.yaml b/k8s/argocd/secrets/secret-generator.yaml index a9d39f1..990b289 100644 --- a/k8s/argocd/secrets/secret-generator.yaml +++ b/k8s/argocd/secrets/secret-generator.yaml @@ -11,6 +11,7 @@ files: - agent-pod-ssh-key.enc.yaml - authentik-secrets.enc.yaml - cloudflare-secrets.enc.yaml + - cloudflared-credentials.enc.yaml - forgejo-registry-pat-api.enc.yaml - forgejo-registry-pull.enc.yaml - forgejo-runner-token.enc.yaml diff --git a/k8s/bootstrap/cert-manager/homelab-ca-configmap.yaml b/k8s/bootstrap/cert-manager/homelab-ca-configmap.yaml index 780e11c..ab80c71 100644 --- a/k8s/bootstrap/cert-manager/homelab-ca-configmap.yaml +++ b/k8s/bootstrap/cert-manager/homelab-ca-configmap.yaml @@ -271,3 +271,40 @@ data: NDKi7/Gxha790QN3TD/U7p9GcEtfXKv8KISKfxnfD9o9Yx6pWOXcgvGefQkpXuRm kGzDM+aY/VKs5Et5/RyKavBDihuOcumLsmoRg/bFoB/xpeURTyiehUdu -----END CERTIFICATE----- +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: homelab-ca + namespace: cloudflared +data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIFBjCCAu6gAwIBAgIJAKoBPdKPDL5AMA0GCSqGSIb3DQEBCwUAMDAxEzARBgNV + BAMMCmhvbWVsYWItY2ExGTAXBgNVBAoMEHJpb3RwaWFvLmhvbWVsYWIwHhcNMjYw + ODEyMjA1NDQzWhcNMzYwODA5MjA1NDQzWjAwMRMwEQYDVQQDDApob21lbGFiLWNh + MRkwFwYDVQQKDBByaW90cGlhby5ob21lbGFiMIICIjANBgkqhkiG9w0BAQEFAAOC + Ag8AMIICCgKCAgEAzw796vZRFOBc/orUu9J6rv91A3SNLJsqz/VuetwAXCaVSlK5 + kL/RaBKKkcXToCC5avwix6W21FnwLUW4R8QlQdin8+XEHpx2LY5R4GmSvaCbU7wm + YwYgVGQVv8KlgcMbz/pF09vXU29cHUHwlkN+BWVGAxVGWxam94j1pBzV0dOhhRw2 + q5sZYUUrPO5l775od2FMMnMzoRFugxDvJof0p3fAChNAIiFhP95lrcb2YsTwK+a2 + 6mk3AZc0gMtmgVsH+zryGFcAmWLixq41ye/H23ktj2bjEHCr9mIDpuSOKLyLBzrg + js/HqdxvwkZkkxwXft0Vi7GN0/LGHMdPrDE499pJelAHbxUpBoOk8sfXSNnfESI8 + v20LybATGrKXFlOhMKshY1a3JLAoZTaW0xkG4PM4HT7lK/ovr/NLF6kdP7HxJFFQ + +4WjySiwbjusNobpTaQWVwCjD3Imf+2feAqJeX3z+P3EhxfrmvEojLOdUiiPcjMn + CFl9u2EpSlax53umSkcBQJGfFKM17fZzhtakjqhCTzAfXXlxZy4hsHDtfzRcfaoB + Zz/PiJ0TtJSC7TvxZZoPpGexQdoeT7kJEksTvGi5OSfJD7HaKe5RQSZGgpz7ATB+ + YMBKbjhMXVu+TAdcO1qZRN0UCncrliN61ki9F0tE8kNgpS9biZ6kheoJMZUCAwEA + AaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcN + AQELBQADggIBAFN1At11d/+Ue6cEjCklnRQpLuD5agXaE+pi5oHNfnggSlO8UjUj + lDzfnmxi3IAI6HruzDARM9J95qJboUxWOAEmK9SNnZNS/1b0GyikP3YcF1gIFqtW + 6SgmVfC1cVUFKGt/+QQzZxhOLuoydv9Ozu12nxsTW/WY2t+cpeL+1wrsM22RA6W/ + xV/bzYbwzCUT9v4tMZLZHk5CkXztVFXSJyW/8w9rVZdUGZM+mwwn81PLSE0v3s/2 + wJH8eUqGl73i3Xgn7CVanN4cJYMGYVVlRD6Qr6muBN4Vh0ETY1PrjzsZ6XsV6vk9 + glMbQnErYMZHZIDtnVdaBt60MecUD9O4eLeip0SE6bVRAucKBXA9xNMilMQEL8/X + FBMAxXl10rowBlbX2oaFSOM80eLTla4a3gSL7+0vua3C4Dtv2bLo8ZjCyqXwOJeb + N85cqcVZStz3qlgQN1nG2qGE3BS6scOs7+mUGe9CmS8acJV++PKm8CtmLH6/ibjV + tAc/6+Vg/keo0kty7H7L6oQd+gKpuaJ2I+wKYe4l9UwHi9zEb8YjzGB4n2AtsoWI + NDKi7/Gxha790QN3TD/U7p9GcEtfXKv8KISKfxnfD9o9Yx6pWOXcgvGefQkpXuRm + kGzDM+aY/VKs5Et5/RyKavBDihuOcumLsmoRg/bFoB/xpeURTyiehUdu + -----END CERTIFICATE----- -- 2.54.0