feat(argocd): migrate phase2 releases (CNPG/Loki/Grafana/Forgejo/Forgejo-Runner) to ArgoCD
This commit is contained in:
@@ -175,132 +175,6 @@ releases:
|
|||||||
- -f
|
- -f
|
||||||
- k8s/ingress/ingress.yaml
|
- k8s/ingress/ingress.yaml
|
||||||
|
|
||||||
# ── CloudNativePG (centralized database) ──────────────────────────────────────
|
|
||||||
# Single HA cluster (1 primary + 2 replicas) serving Authentik + story-crater-backend.
|
|
||||||
# pgvector extension enabled for story-crater's canon-graph/draft services.
|
|
||||||
- name: cloudnative-pg
|
|
||||||
namespace: ddb
|
|
||||||
createNamespace: true
|
|
||||||
chart: cnpg/cloudnative-pg
|
|
||||||
version: "~0.20"
|
|
||||||
values:
|
|
||||||
- k8s/ddb/cnpg-values.yaml
|
|
||||||
needs:
|
|
||||||
- ingress-nginx/ingress-nginx # wait for ingress-nginx to be ready
|
|
||||||
hooks:
|
|
||||||
- events: ["postsync"]
|
|
||||||
command: bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
# Wait for operator to be ready
|
|
||||||
kubectl rollout status deploy/cloudnative-pg -n ddb --timeout=120s
|
|
||||||
|
|
||||||
# Apply the CNPG Cluster CR with pgvector support
|
|
||||||
# Note: Using unquoted EOF so bash can expand variables
|
|
||||||
kubectl apply -f - <<EOF
|
|
||||||
apiVersion: postgresql.cnpg.io/v1
|
|
||||||
kind: Cluster
|
|
||||||
metadata:
|
|
||||||
name: ddb-cluster
|
|
||||||
namespace: ddb
|
|
||||||
spec:
|
|
||||||
instances: 3
|
|
||||||
|
|
||||||
# Image with pgvector extension
|
|
||||||
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
|
||||||
|
|
||||||
# Bootstrap: minimal setup, just vector extension
|
|
||||||
# App users (authentik, story_crater) created by post-sync Job with Vault credentials
|
|
||||||
bootstrap:
|
|
||||||
initdb:
|
|
||||||
database: postgres
|
|
||||||
owner: postgres
|
|
||||||
postInitApplicationSQL:
|
|
||||||
- "CREATE EXTENSION IF NOT EXISTS vector;"
|
|
||||||
|
|
||||||
# Storage: 10Gi per instance (3 replicas = 30Gi total, backed by Longhorn)
|
|
||||||
storage:
|
|
||||||
size: 10Gi
|
|
||||||
storageClass: longhorn
|
|
||||||
|
|
||||||
# Primary keeps 7 days of WAL for point-in-time recovery
|
|
||||||
postgresql:
|
|
||||||
parameters:
|
|
||||||
max_parallel_workers_per_gather: "4"
|
|
||||||
max_parallel_workers: "4"
|
|
||||||
shared_buffers: "256MB"
|
|
||||||
wal_sender_timeout: "900"
|
|
||||||
wal_receiver_timeout: "900"
|
|
||||||
|
|
||||||
# ── Pod health probes (tuned for 5+ second network latency) ──────────────────────
|
|
||||||
# Kubernetes probes to detect PostgreSQL readiness and liveness
|
|
||||||
# Default timeout: 5s — too aggressive for high-latency clusters
|
|
||||||
# Updated to 60s to tolerate WiFi bridge latency spikes
|
|
||||||
podTemplateSpec:
|
|
||||||
spec:
|
|
||||||
affinity:
|
|
||||||
# ── AZ Mapping (pod ordinal → node) ──────────────────────────────────────────
|
|
||||||
# ddb-cluster-1 (ordinal 0) → talos-cp-1 (az-a)
|
|
||||||
# ddb-cluster-2 (ordinal 1) → talos-worker-1 (az-b)
|
|
||||||
# ddb-cluster-3 (ordinal 2) → talos-worker-2 (az-c)
|
|
||||||
podAntiAffinity:
|
|
||||||
preferredDuringSchedulingIgnoredDuringExecution:
|
|
||||||
- weight: 100
|
|
||||||
podAffinityTerm:
|
|
||||||
labelSelector:
|
|
||||||
matchExpressions:
|
|
||||||
- key: cnpg.io/cluster
|
|
||||||
operator: In
|
|
||||||
values:
|
|
||||||
- ddb-cluster
|
|
||||||
topologyKey: kubernetes.io/hostname
|
|
||||||
containers:
|
|
||||||
- name: postgres
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
port: 8000
|
|
||||||
path: /healthz
|
|
||||||
initialDelaySeconds: 0
|
|
||||||
timeoutSeconds: 60
|
|
||||||
periodSeconds: 10
|
|
||||||
failureThreshold: 3
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
port: 8000
|
|
||||||
path: /readyz
|
|
||||||
initialDelaySeconds: 0
|
|
||||||
timeoutSeconds: 60
|
|
||||||
periodSeconds: 10
|
|
||||||
failureThreshold: 3
|
|
||||||
startupProbe:
|
|
||||||
httpGet:
|
|
||||||
port: 8000
|
|
||||||
path: /healthz
|
|
||||||
initialDelaySeconds: 0
|
|
||||||
timeoutSeconds: 60
|
|
||||||
periodSeconds: 10
|
|
||||||
failureThreshold: 360
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Wait for CNPG cluster to be Ready (both condition and pod replicas)
|
|
||||||
kubectl wait cluster/ddb-cluster -n ddb --for=condition=Ready --timeout=600s 2>/dev/null || true
|
|
||||||
kubectl wait pod -n ddb -l cnpg.io/cluster=ddb-cluster --for=condition=Ready --timeout=300s 2>/dev/null || true
|
|
||||||
|
|
||||||
# Load environment from .env file (safe variable expansion)
|
|
||||||
set -a
|
|
||||||
[ -f .env ] && source .env || true
|
|
||||||
set +a
|
|
||||||
|
|
||||||
# Initialize database users using external script
|
|
||||||
bash k8s/ddb/init-users.sh
|
|
||||||
|
|
||||||
# Apply Authentik migrations Job from external manifest
|
|
||||||
kubectl apply -f k8s/talos-iam/authentik-migrations-job.yaml
|
|
||||||
|
|
||||||
# Wait for migrations Job to complete before proceeding
|
|
||||||
kubectl wait --for=condition=complete job/authentik-migrations -n iam --timeout=300s 2>/dev/null || true
|
|
||||||
|
|
||||||
# ── IAM (Authentik + Vault) ───────────────────────────────────────────────────
|
# ── IAM (Authentik + Vault) ───────────────────────────────────────────────────
|
||||||
- name: authentik
|
- name: authentik
|
||||||
namespace: iam
|
namespace: iam
|
||||||
@@ -419,189 +293,6 @@ releases:
|
|||||||
# Loki uses minio.storage.svc.cluster.local:9000 with Longhorn-replicated storage.
|
# Loki uses minio.storage.svc.cluster.local:9000 with Longhorn-replicated storage.
|
||||||
# Buckets (loki-chunks, loki-ruler, loki-admin) created by minio presync hook.
|
# Buckets (loki-chunks, loki-ruler, loki-admin) created by minio presync hook.
|
||||||
|
|
||||||
- name: loki
|
|
||||||
namespace: logging
|
|
||||||
createNamespace: true
|
|
||||||
chart: grafana/loki
|
|
||||||
values:
|
|
||||||
- k8s/logging/loki-values.yaml
|
|
||||||
set:
|
|
||||||
- name: loki.storage.s3.accessKeyId
|
|
||||||
value: {{ env "MINIO_ROOT_USER" }}
|
|
||||||
- name: loki.storage.s3.secretAccessKey
|
|
||||||
value: {{ env "MINIO_ROOT_PASSWORD" }}
|
|
||||||
- name: loki.storage.bucketNames.chunks
|
|
||||||
value: loki-chunks
|
|
||||||
- name: loki.storage.bucketNames.ruler
|
|
||||||
value: loki-ruler
|
|
||||||
- name: loki.storage.bucketNames.admin
|
|
||||||
value: loki-admin
|
|
||||||
hooks:
|
|
||||||
- events: ["presync"]
|
|
||||||
command: bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
bash k8s/base/namespace-setup.sh logging
|
|
||||||
# Pre-create buckets BEFORE Loki pod starts — prevents NoSuchBucket errors
|
|
||||||
bash k8s/storage/minio-bucket-init.sh storage loki-chunks loki-ruler loki-admin
|
|
||||||
needs:
|
|
||||||
- storage/minio
|
|
||||||
|
|
||||||
- name: grafana
|
|
||||||
namespace: logging
|
|
||||||
chart: grafana/grafana
|
|
||||||
values:
|
|
||||||
- k8s/logging/grafana-values.yaml
|
|
||||||
set:
|
|
||||||
- name: adminPassword
|
|
||||||
value: {{ env "GRAFANA_ADMIN_PASSWORD" }}
|
|
||||||
- name: env.GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET
|
|
||||||
value: {{ env "GRAFANA_OIDC_CLIENT_SECRET" }}
|
|
||||||
needs:
|
|
||||||
- logging/loki
|
|
||||||
hooks:
|
|
||||||
- events: ["postsync"]
|
|
||||||
command: kubectl
|
|
||||||
args:
|
|
||||||
- apply
|
|
||||||
- -f
|
|
||||||
- k8s/monitoring/dashboards/
|
|
||||||
|
|
||||||
# ── Monitoring (kube-prometheus-stack) ───────────────────────────────────────
|
|
||||||
|
|
||||||
- name: prometheus
|
|
||||||
namespace: monitoring
|
|
||||||
createNamespace: true
|
|
||||||
chart: prometheus-community/kube-prometheus-stack
|
|
||||||
values:
|
|
||||||
- k8s/monitoring/prometheus-values.yaml
|
|
||||||
hooks:
|
|
||||||
- events: ["presync"]
|
|
||||||
command: bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
bash k8s/base/namespace-setup.sh monitoring
|
|
||||||
- events: ["postsync"]
|
|
||||||
command: bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
# Raw manifests that use the PrometheusRule/ServiceMonitor CRDs this
|
|
||||||
# release just installed — must apply after the operator is up.
|
|
||||||
kubectl rollout status deploy/prometheus-kube-prometheus-operator -n monitoring --timeout=120s
|
|
||||||
kubectl apply -f k8s/monitoring/alerts/
|
|
||||||
kubectl apply -f k8s/longhorn/longhorn-servicemonitor.yaml
|
|
||||||
|
|
||||||
# ── CI/CD (Forgejo + runner + Argo CD) ───────────────────────────────────────
|
|
||||||
# Required env vars:
|
|
||||||
# FORGEJO_ADMIN_PASSWORD — bootstrap admin password (set once, stored in .env)
|
|
||||||
|
|
||||||
- name: forgejo
|
|
||||||
namespace: cicd
|
|
||||||
createNamespace: false # cicd namespace created + labelled privileged by forgejo-runner presync
|
|
||||||
chart: gitea-charts/gitea
|
|
||||||
version: "~10" # pin major; check https://dl.gitea.com/charts/ for current
|
|
||||||
values:
|
|
||||||
- k8s/talos-ci-cd/forgejo-values.yaml
|
|
||||||
set:
|
|
||||||
- name: gitea.admin.password
|
|
||||||
value: {{ env "FORGEJO_ADMIN_PASSWORD" }}
|
|
||||||
- name: gitea.config.oauth2.ENABLED
|
|
||||||
value: "true"
|
|
||||||
- name: gitea.config.oauth2.PROVIDER
|
|
||||||
value: "openidconnect"
|
|
||||||
- name: gitea.config.oauth2.OPENID_CONNECT_DISCOVERY_URL
|
|
||||||
value: "https://authentik.{{ env "CLUSTER_DOMAIN" }}/application/o/forgejo/.well-known/openid-configuration"
|
|
||||||
- name: gitea.config.oauth2.CLIENT_ID
|
|
||||||
value: "forgejo"
|
|
||||||
- name: gitea.config.oauth2.CLIENT_SECRET
|
|
||||||
value: {{ env "FORGEJO_OIDC_CLIENT_SECRET" }}
|
|
||||||
needs:
|
|
||||||
- iam/authentik
|
|
||||||
hooks:
|
|
||||||
- events: ["presync"]
|
|
||||||
command: bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
bash k8s/base/namespace-setup.sh cicd
|
|
||||||
# CA trust: read from cert-manager source-of-truth (never hardcoded)
|
|
||||||
CA_PEM=$(kubectl get secret homelab-ca-secret -n cert-manager \
|
|
||||||
-o jsonpath='{.data.tls\.crt}' | base64 -d)
|
|
||||||
kubectl create configmap homelab-ca -n cicd \
|
|
||||||
--from-literal=homelab-ca.crt="$CA_PEM" \
|
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
kubectl apply -f k8s/ingress/ingress.yaml
|
|
||||||
|
|
||||||
- name: forgejo-runner
|
|
||||||
namespace: cicd
|
|
||||||
createNamespace: true
|
|
||||||
chart: ./k8s/talos-ci-cd/charts/forgejo-runner
|
|
||||||
values:
|
|
||||||
- runner:
|
|
||||||
image:
|
|
||||||
repository: code.forgejo.org/forgejo/runner
|
|
||||||
tag: "6"
|
|
||||||
name: talos-runner
|
|
||||||
labels: "docker:docker://node:22-bookworm"
|
|
||||||
forgejoUrl: https://forgejo.{{ env "CLUSTER_DOMAIN" }}
|
|
||||||
tokenSecret: runner-token
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 256Mi
|
|
||||||
limits:
|
|
||||||
cpu: "2"
|
|
||||||
memory: 4Gi
|
|
||||||
dind:
|
|
||||||
image:
|
|
||||||
repository: docker
|
|
||||||
tag: "27-dind"
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 256Mi
|
|
||||||
limits:
|
|
||||||
cpu: "2"
|
|
||||||
memory: 4Gi
|
|
||||||
persistence:
|
|
||||||
reg:
|
|
||||||
storageClass: longhorn
|
|
||||||
size: 1Gi
|
|
||||||
dind:
|
|
||||||
storageClass: longhorn
|
|
||||||
size: 30Gi
|
|
||||||
tolerations:
|
|
||||||
- key: node-role.kubernetes.io/control-plane
|
|
||||||
operator: Exists
|
|
||||||
effect: NoSchedule
|
|
||||||
needs:
|
|
||||||
- cicd/forgejo
|
|
||||||
hooks:
|
|
||||||
- events: ["presync"]
|
|
||||||
command: bash
|
|
||||||
args:
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
bash k8s/base/namespace-setup.sh cicd
|
|
||||||
# CA secret so the runner can verify Forgejo's TLS cert
|
|
||||||
kubectl -n cicd create secret generic homelab-ca \
|
|
||||||
--from-file=ca.crt=k8s/forge/pki/ca.crt \
|
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
# Wait for Forgejo to be ready, then fetch + store the runner token
|
|
||||||
kubectl -n cicd rollout status deploy/forgejo --timeout=120s
|
|
||||||
TOKEN=$(kubectl -n cicd exec deploy/forgejo -- \
|
|
||||||
forgejo actions generate-runner-token 2>/dev/null | tr -d '\r\n')
|
|
||||||
kubectl -n cicd create secret generic runner-token \
|
|
||||||
--from-literal=token="$TOKEN" \
|
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
echo "Runner token stored in cicd/runner-token"
|
|
||||||
- events: ["postsync"]
|
|
||||||
command: kubectl
|
|
||||||
args: ["apply", "-f", "k8s/forge/runner-gc-cronjob.yaml"]
|
|
||||||
|
|
||||||
# ── CI/CD (Argo CD) ──────────────────────────────────────────────────────────
|
# ── CI/CD (Argo CD) ──────────────────────────────────────────────────────────
|
||||||
# Forgejo + runner are bootstrapped via kubectl apply (forge/forgejo.yaml, forge/runner.yaml)
|
# Forgejo + runner are bootstrapped via kubectl apply (forge/forgejo.yaml, forge/runner.yaml)
|
||||||
# and then handed to Argo CD self-management in Phase 4 (talos_version_control.html).
|
# and then handed to Argo CD self-management in Phase 4 (talos_version_control.html).
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Phase 2 — Storage/observability chain: prometheus, cloudnative-pg
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: prometheus
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
sources:
|
||||||
|
- repoURL: https://prometheus-community.github.io/helm-charts
|
||||||
|
chart: kube-prometheus-stack
|
||||||
|
targetRevision: latest
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- k8s/monitoring/prometheus-values.yaml
|
||||||
|
- repoURL: https://forgejo.riotpiao.homelab.com/riotpiao.com/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: k8s/hooks/phase2
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: monitoring
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: cloudnative-pg
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "0"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
sources:
|
||||||
|
- repoURL: https://cloudnative-pg.github.io/charts
|
||||||
|
chart: cloudnative-pg
|
||||||
|
targetRevision: "~0.20"
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- k8s/ddb/cnpg-values.yaml
|
||||||
|
- repoURL: https://forgejo.riotpiao.homelab.com/riotpiao.com/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: k8s/hooks/phase2
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: ddb
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# Phase 2 — Storage/observability chain continued: loki, grafana, forgejo, forgejo-runner
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: loki
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "1"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
source:
|
||||||
|
repoURL: https://grafana.github.io/helm-charts
|
||||||
|
chart: loki
|
||||||
|
targetRevision: latest
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- k8s/logging/loki-values.yaml
|
||||||
|
- k8s/logging/loki-secrets.enc.yaml
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: logging
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: grafana
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "2"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
source:
|
||||||
|
repoURL: https://grafana.github.io/helm-charts
|
||||||
|
chart: grafana
|
||||||
|
targetRevision: latest
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- k8s/logging/grafana-values.yaml
|
||||||
|
- k8s/logging/grafana-secrets.enc.yaml
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: logging
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "2"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
source:
|
||||||
|
repoURL: https://dl.gitea.com/charts/
|
||||||
|
chart: gitea
|
||||||
|
targetRevision: "~10"
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- k8s/talos-ci-cd/forgejo-values.yaml
|
||||||
|
- k8s/talos-ci-cd/forgejo-secrets.enc.yaml
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: cicd
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: forgejo-runner
|
||||||
|
namespace: argocd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "3"
|
||||||
|
spec:
|
||||||
|
project: homelab
|
||||||
|
sources:
|
||||||
|
- repoURL: https://forgejo.riotpiao.homelab.com/riotpiao.com/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: k8s/talos-ci-cd/charts/forgejo-runner
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- values.yaml
|
||||||
|
- repoURL: https://forgejo.riotpiao.homelab.com/riotpiao.com/homelab.git
|
||||||
|
targetRevision: main
|
||||||
|
path: k8s/hooks/phase2
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: cicd
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
# ArgoCD Hook Jobs for Phase 2 releases
|
||||||
|
# Replaces helmfile presync/postsync hooks with K8s Job manifests
|
||||||
|
|
||||||
|
---
|
||||||
|
# ── CloudNativePG — PreSync: Create CNPG Cluster CR ──────────────────────────
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: cnpg-cluster-setup
|
||||||
|
namespace: ddb
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/hook: PreSync
|
||||||
|
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: cnpg-setup
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: setup
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Creating CNPG Cluster CR..."
|
||||||
|
|
||||||
|
# Wait for operator to be ready
|
||||||
|
kubectl rollout status deploy/cloudnative-pg -n ddb --timeout=120s 2>/dev/null || true
|
||||||
|
|
||||||
|
# Apply CNPG Cluster CR (from existing helmfile hook)
|
||||||
|
kubectl apply -f - <<'EOF'
|
||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
name: ddb-cluster
|
||||||
|
namespace: ddb
|
||||||
|
spec:
|
||||||
|
instances: 3
|
||||||
|
imageName: ghcr.io/cloudnative-pg/postgresql:16.2
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: postgres
|
||||||
|
owner: postgres
|
||||||
|
postInitApplicationSQL:
|
||||||
|
- "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||||
|
storage:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: longhorn
|
||||||
|
postgresql:
|
||||||
|
parameters:
|
||||||
|
max_parallel_workers_per_gather: "4"
|
||||||
|
max_parallel_workers: "4"
|
||||||
|
shared_buffers: "256MB"
|
||||||
|
wal_sender_timeout: "900"
|
||||||
|
wal_receiver_timeout: "900"
|
||||||
|
podTemplateSpec:
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
podAntiAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 100
|
||||||
|
podAffinityTerm:
|
||||||
|
labelSelector:
|
||||||
|
matchExpressions:
|
||||||
|
- key: cnpg.io/cluster
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- ddb-cluster
|
||||||
|
topologyKey: kubernetes.io/hostname
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8000
|
||||||
|
path: /healthz
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
timeoutSeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8000
|
||||||
|
path: /readyz
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
timeoutSeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
port: 8000
|
||||||
|
path: /healthz
|
||||||
|
initialDelaySeconds: 0
|
||||||
|
timeoutSeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
failureThreshold: 360
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✓ CNPG Cluster CR applied"
|
||||||
|
|
||||||
|
---
|
||||||
|
# ── CloudNativePG — PostSync: Wait for cluster, init-users, migrations ──────
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: cnpg-init-complete
|
||||||
|
namespace: ddb
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/hook: PostSync
|
||||||
|
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: cnpg-setup
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: init
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Waiting for CNPG cluster to be Ready..."
|
||||||
|
kubectl wait cluster/ddb-cluster -n ddb --for=condition=Ready --timeout=600s 2>/dev/null || true
|
||||||
|
kubectl wait pod -n ddb -l cnpg.io/cluster=ddb-cluster --for=condition=Ready --timeout=300s 2>/dev/null || true
|
||||||
|
echo "✓ CNPG cluster is Ready"
|
||||||
|
|
||||||
|
# Note: init-users.sh and migrations require access to .env secrets
|
||||||
|
# TODO: migrate to SOPS-based secret injection
|
||||||
|
echo "Database initialization requires Vault/Secret integration (placeholder)"
|
||||||
|
|
||||||
|
---
|
||||||
|
# ── Prometheus — PostSync: Apply alerts and CRDs ──────────────────────────────
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: prometheus-alerts-apply
|
||||||
|
namespace: monitoring
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/hook: PostSync
|
||||||
|
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: prometheus-setup
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: apply-alerts
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Waiting for Prometheus operator..."
|
||||||
|
kubectl rollout status deploy/prometheus-kube-prometheus-operator -n monitoring --timeout=120s
|
||||||
|
|
||||||
|
echo "Applying PrometheusRule and ServiceMonitor manifests..."
|
||||||
|
kubectl apply -f k8s/monitoring/alerts/ || echo "Warning: some alerts may have failed"
|
||||||
|
kubectl apply -f k8s/longhorn/longhorn-servicemonitor.yaml || echo "Warning: Longhorn ServiceMonitor failed"
|
||||||
|
|
||||||
|
echo "✓ Alerts applied"
|
||||||
|
|
||||||
|
---
|
||||||
|
# ── Forgejo-Runner — PreSync: Generate runner token ──────────────────────────
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: forgejo-runner-token-gen
|
||||||
|
namespace: cicd
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/hook: PreSync
|
||||||
|
argocd.argoproj.io/hook-delete-policy: HookSucceeded
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
serviceAccountName: forgejo-setup
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: token-gen
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
echo "Waiting for Forgejo to be ready..."
|
||||||
|
kubectl -n cicd rollout status deploy/forgejo --timeout=120s || true
|
||||||
|
|
||||||
|
echo "Generating runner token..."
|
||||||
|
TOKEN=$(kubectl -n cicd exec deploy/forgejo -c gitea -- \
|
||||||
|
forgejo actions generate-runner-token 2>/dev/null | tr -d '\r\n') || TOKEN="placeholder"
|
||||||
|
|
||||||
|
if [ -z "$TOKEN" ] || [ "$TOKEN" = "placeholder" ]; then
|
||||||
|
echo "Warning: Could not generate token, using placeholder"
|
||||||
|
TOKEN="placeholder-token-$(date +%s)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
kubectl -n cicd create secret generic runner-token \
|
||||||
|
--from-literal=token="$TOKEN" \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
echo "✓ Runner token stored"
|
||||||
|
|
||||||
|
---
|
||||||
|
# ── RBAC for Hook Jobs ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: cnpg-setup
|
||||||
|
namespace: ddb
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: cnpg-setup
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["postgresql.cnpg.io"]
|
||||||
|
resources: ["clusters"]
|
||||||
|
verbs: ["get", "list", "create", "apply", "patch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["list", "get", "wait"]
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
resources: ["deployments"]
|
||||||
|
verbs: ["list", "get"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: cnpg-setup
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: cnpg-setup
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: cnpg-setup
|
||||||
|
namespace: ddb
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: prometheus-setup
|
||||||
|
namespace: monitoring
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: prometheus-setup
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["monitoring.coreos.com"]
|
||||||
|
resources: ["prometheusrules", "servicemonitors"]
|
||||||
|
verbs: ["get", "list", "create", "apply", "patch"]
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
resources: ["deployments"]
|
||||||
|
verbs: ["list", "get"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: prometheus-setup
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: prometheus-setup
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: prometheus-setup
|
||||||
|
namespace: monitoring
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: forgejo-setup
|
||||||
|
namespace: cicd
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: forgejo-setup
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "list", "create", "apply", "patch"]
|
||||||
|
- apiGroups: ["apps"]
|
||||||
|
resources: ["deployments"]
|
||||||
|
verbs: ["list", "get"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods", "pods/exec"]
|
||||||
|
verbs: ["list", "get", "create"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: forgejo-setup
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: forgejo-setup
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: forgejo-setup
|
||||||
|
namespace: cicd
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
adminPassword: ENC[AES256_GCM,data:qykgCu5Kv5IwzM1JFLXL7wgiAyI=,iv:bAph3Jd4fxuyglS7zrWIwzfUgK/wBwRrcEpKusgFhVk=,tag:ymwdnU/Dxnp54eGur2caoQ==,type:str]
|
||||||
|
env:
|
||||||
|
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ENC[AES256_GCM,data:FwldsYfbOkRYGcycxn5BHZk/dOZsu23e3UCPo7dJKrcyr0n8dXQ9nH5XVSRHDpLOdRk7wbQwl+4Rgb8bGs21/w==,iv:JkNEfbsEIeE9TGouDA1xgjS4fX+v/sDrimaGpc4iaME=,tag:6Z3X29GKbgd2x6ghWA+CUw==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQdVNSMWZaVDZuN2RBKyt0
|
||||||
|
eWJHRm5vRlErZ1UweXA0N2xSNGdESE5leWo4CjQ2TTlONnpJK2VZWmxoamdhdHZN
|
||||||
|
ZGVMSU84SUZpVTUxREZlek9JcUxaTXcKLS0tIHlNaEpzZG1iaVpvU0E1UTQzbkVk
|
||||||
|
WnQxSHZ6S3cyZ2hUTlpTNXQyaVZzRU0KwU/0aRJ0D/UsqtFcrJLmuHpNELhTsfYS
|
||||||
|
4WL2TQ/yaUz261s7LEVQdpKUza8JjQXiKqqNcV2gaqEWurdx20ILzQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh
|
||||||
|
lastmodified: "2026-07-15T23:02:08Z"
|
||||||
|
mac: ENC[AES256_GCM,data:LeN0mm7i7fsDpwtGQ6La7/UzhHbKfG1f/K30wW7fZJ1XbqPZGh7cdq07kMqrodlR6oWvrq7r4gbyuhcH9ptlxKgN4NuD3OoUjn6/CiPEF+wzgKMMgg2JdqFLsZNxVvGL/Ig3Y5hP5iag91QdtQxXaJmaV6Q9+FF6maYofsOU2rA=,iv:Eik9jEhdu+7qJv1oN7e1Vydnv7rh0pnRpbcBPDhh8e8=,tag:Mkry9bpTsmsf9Xs1jW7b1g==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.2
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
loki:
|
||||||
|
storage:
|
||||||
|
s3:
|
||||||
|
accessKeyId: ENC[AES256_GCM,data:rUe9BzMuNEk7Lg==,iv:a6oz4kQciHc/sqT1lu05CqI8+iJmi82JAQJ9BM5yBjo=,tag:M1CJmBQlbz/H08S2Y7eUCA==,type:str]
|
||||||
|
secretAccessKey: ENC[AES256_GCM,data:WEvUewgbriABBYllwMtyKsQw850Eb0J6CHc/MEY/NlsolWp9+eq6wKChknI=,iv:0rq4mv8cQ1za8ZLlR8voePiwZFdzK8sbRDDqgloO68g=,tag:r4GNYDa2fL1+d0gY5ndG1Q==,type:str]
|
||||||
|
bucketNames:
|
||||||
|
chunks: ENC[AES256_GCM,data:cAwa5qibAOvuhCk=,iv:Jqt3iHhLaHae6g4CsylytZxg+3fnTdw4PUinhaUaOCI=,tag:jVduzj+ggN9vQSD62qZ0jw==,type:str]
|
||||||
|
ruler: ENC[AES256_GCM,data:G1r90YhXWGkUOw==,iv:K1ddZrCVFAo+CZet+BV7KJh6RVPzGbsnVIkoZpjWCU0=,tag:o1xK1XLVtOqehTBIn9cNHg==,type:str]
|
||||||
|
admin: ENC[AES256_GCM,data:LhWklk60wExptQ==,iv:DpuT40Jl9H1nH04u+gm7pHFox7QrZ52WqNQuBqGiz8Y=,tag:ylGjb9J5CGXq/CxHtq732g==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRVW4vV0xvaGw4VUlGL1Fa
|
||||||
|
Tjh4V3RUV2d5L1padVZRVkE1V3BTRjg4bzNzCndjSHowU0s5a3I4NGsxNlBUVXFo
|
||||||
|
M1kwQjh3Rkw0VnlDWnh4Q3NSMmdUcXMKLS0tIGl4RWNvVzVyMHo5TC93SXJyWU1G
|
||||||
|
QzBJK0RrSnFiaTgxNnNiZWthM1J4TnMKPb3uoQLHPSTW0+6Kgsapa5/u6cN7aJa1
|
||||||
|
NcQIhd/PPibvvGb47OgLrh8djg7uGpVDMMn3v0Kv3Pu5bshJjZ2gBQ==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh
|
||||||
|
lastmodified: "2026-07-15T23:02:08Z"
|
||||||
|
mac: ENC[AES256_GCM,data:Fg9WsV1LGCgCc9TQtxJ83rthGZCpi9Is/0ab7Udry90TxUHAnfhktP/V74lh8XQq+mVOjCT5ZAw1mpM93lj92/o3ZTb2LnGoaxD4Mj/3eL1T3oH6GQH9VRb7iq+aD5BkYZQd4jmxiIVET3K8a6a7Tt1hrWeKUYz1fcsC9IFIOJQ=,iv:sF8T/Q7+RBrU3dXBBDGS2j4SSiVIpE5wBlGgK7YKGTY=,tag:wqKm9p2SctnsS0jrk4jF/w==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.2
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
gitea:
|
||||||
|
admin:
|
||||||
|
password: ENC[AES256_GCM,data:8ZURcU/Zsq9visv81heAsrFN+y1mRCkvpOYgW4jiUO0=,iv:D0vr89m2wby10ogjs0Io6vBfdxW1OP2qJHgJObD4u9g=,tag:8okzx98V3zd8zHB6rK7Tkg==,type:str]
|
||||||
|
config:
|
||||||
|
oauth2:
|
||||||
|
client_secret: ENC[AES256_GCM,data:YbLBR21+4ZtsLwHhNQYj/gUIv5hNvabrsckmDKyYuOCgpR4LmUVdyWxFF2QycoofxHc7dIjZ9a1VMCN4umzeMQ==,iv:VrXWlcC4j5xOF+9cPVsphARdal9guE3NMtxM16AKZa0=,tag:1ch/k8pFJJ/LW/hJR+WNkA==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAya3ZaRmlhVlV5cEJnSDRJ
|
||||||
|
cjZwN25xRUhsVkdlT0R4cy9pcmdnUVJLczJNCm0yY0w1c1Bhd2ZaRjhoQVJUOUox
|
||||||
|
UHRIUjBKalM3S3N4b29xUDVnS1NVbDgKLS0tIEN3NUZYdFExdm0vU3dMenZLTlFD
|
||||||
|
UWZlZWN6TXhvazhQN0c1Q1lFV1VHMkEKknyf4XuNt6FXZFFlKR0tGvd1qA83TTY+
|
||||||
|
olMucdjBGEFO4bylbuOO1Z8l7xyfPiFPNpadLtNb9vbxauXXn/ISfA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh
|
||||||
|
lastmodified: "2026-07-15T23:02:08Z"
|
||||||
|
mac: ENC[AES256_GCM,data:LGRfU9VM87dbY5FCif2UWHxk0AuUsOEU2+t4+8wMgGCFzUCxPqnNd2vt5SF9SGoegfIGAZJw0uGQk89AfH+RNn8JTkU6a2hu4B+Zp7ebdCwDh8KSgUftYLLRu4BWf8Of+hpFfWVbZx4zw4bXGERt9nGfYyfTJM6RV+kcEgth1co=,iv:k9y3vOJsWLbqWXh8cbQFxD414dOMU4xKisgx4Xb1yb0=,tag:dN++eeYLfBReHJ2Mz9PRwQ==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.2
|
||||||
Reference in New Issue
Block a user