From cab0866e14749b9b00efa88dfe5a740d1f9ad10e Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Wed, 22 Apr 2026 13:31:09 +0200 Subject: [PATCH] multi-cloud no mcp --- README.md | 40 +++++--- _app-of-apps-aws-dev.yaml | 32 ++++++ _app-of-apps-aws-prod.yaml | 32 ++++++ _app-of-apps-azure-dev.yaml | 32 ++++++ _app-of-apps-azure-prod.yaml | 32 ++++++ _app-of-apps-gcp-dev.yaml | 32 ++++++ _app-of-apps-gcp-prod.yaml | 32 ++++++ cluster-resources/gitea-backup-cronjob.yaml | 6 +- clusters/aws-dev.yaml | 10 ++ clusters/aws-prod.yaml | 10 ++ clusters/azure-dev.yaml | 10 ++ clusters/azure-prod.yaml | 10 ++ clusters/gcp-dev.yaml | 10 ++ clusters/gcp-prod.yaml | 10 ++ docs/GITOPS-ARCHITECTURE.md | 49 +++++---- docs/OPERATIONS-RUNBOOK.md | 68 +++++++++---- docs/README.md | 11 +- docs/REFERENCE.md | 22 ++-- infra/base/gitea.yaml | 1 + infra/base/opencost.yaml | 1 + infra/overlays/aws-dev/kustomization.yaml | 35 +++++++ infra/overlays/aws-prod/kustomization.yaml | 35 +++++++ infra/overlays/azure-dev/kustomization.yaml | 35 +++++++ infra/overlays/azure-prod/kustomization.yaml | 35 +++++++ infra/overlays/gcp-dev/kustomization.yaml | 35 +++++++ infra/overlays/gcp-prod/kustomization.yaml | 35 +++++++ infra/overlays/upc-prod/kustomization.yaml | 18 ++++ infra/values/aws-dev/gitea-values.yaml | 7 ++ infra/values/aws-dev/opencost-values.yaml | 13 +++ infra/values/aws-dev/traefik-values.yaml | 18 ++++ infra/values/aws-prod/gitea-values.yaml | 7 ++ infra/values/aws-prod/opencost-values.yaml | 13 +++ infra/values/aws-prod/traefik-values.yaml | 18 ++++ infra/values/azure-dev/gitea-values.yaml | 7 ++ infra/values/azure-dev/opencost-values.yaml | 11 ++ infra/values/azure-dev/traefik-values.yaml | 16 +++ infra/values/azure-prod/gitea-values.yaml | 7 ++ infra/values/azure-prod/opencost-values.yaml | 11 ++ infra/values/azure-prod/traefik-values.yaml | 16 +++ infra/values/base/gitea-values.yaml | 2 - infra/values/base/opencost-values.yaml | 14 +-- infra/values/gcp-dev/gitea-values.yaml | 7 ++ infra/values/gcp-dev/opencost-values.yaml | 9 ++ infra/values/gcp-dev/traefik-values.yaml | 15 +++ infra/values/gcp-prod/gitea-values.yaml | 7 ++ infra/values/gcp-prod/opencost-values.yaml | 9 ++ infra/values/gcp-prod/traefik-values.yaml | 15 +++ infra/values/upc-dev/gitea-values.yaml | 7 ++ infra/values/upc-dev/opencost-values.yaml | 15 +++ infra/values/upc-prod/gitea-values.yaml | 7 ++ infra/values/upc-prod/opencost-values.yaml | 15 +++ scripts/gitea-backup-aws.sh | 94 +++++++++++++++++ scripts/gitea-backup-azure.sh | 100 +++++++++++++++++++ scripts/gitea-backup-gcp.sh | 95 ++++++++++++++++++ 54 files changed, 1150 insertions(+), 83 deletions(-) create mode 100644 _app-of-apps-aws-dev.yaml create mode 100644 _app-of-apps-aws-prod.yaml create mode 100644 _app-of-apps-azure-dev.yaml create mode 100644 _app-of-apps-azure-prod.yaml create mode 100644 _app-of-apps-gcp-dev.yaml create mode 100644 _app-of-apps-gcp-prod.yaml create mode 100644 clusters/aws-dev.yaml create mode 100644 clusters/aws-prod.yaml create mode 100644 clusters/azure-dev.yaml create mode 100644 clusters/azure-prod.yaml create mode 100644 clusters/gcp-dev.yaml create mode 100644 clusters/gcp-prod.yaml create mode 100644 infra/overlays/aws-dev/kustomization.yaml create mode 100644 infra/overlays/aws-prod/kustomization.yaml create mode 100644 infra/overlays/azure-dev/kustomization.yaml create mode 100644 infra/overlays/azure-prod/kustomization.yaml create mode 100644 infra/overlays/gcp-dev/kustomization.yaml create mode 100644 infra/overlays/gcp-prod/kustomization.yaml create mode 100644 infra/values/aws-dev/gitea-values.yaml create mode 100644 infra/values/aws-dev/opencost-values.yaml create mode 100644 infra/values/aws-dev/traefik-values.yaml create mode 100644 infra/values/aws-prod/gitea-values.yaml create mode 100644 infra/values/aws-prod/opencost-values.yaml create mode 100644 infra/values/aws-prod/traefik-values.yaml create mode 100644 infra/values/azure-dev/gitea-values.yaml create mode 100644 infra/values/azure-dev/opencost-values.yaml create mode 100644 infra/values/azure-dev/traefik-values.yaml create mode 100644 infra/values/azure-prod/gitea-values.yaml create mode 100644 infra/values/azure-prod/opencost-values.yaml create mode 100644 infra/values/azure-prod/traefik-values.yaml create mode 100644 infra/values/gcp-dev/gitea-values.yaml create mode 100644 infra/values/gcp-dev/opencost-values.yaml create mode 100644 infra/values/gcp-dev/traefik-values.yaml create mode 100644 infra/values/gcp-prod/gitea-values.yaml create mode 100644 infra/values/gcp-prod/opencost-values.yaml create mode 100644 infra/values/gcp-prod/traefik-values.yaml create mode 100644 infra/values/upc-dev/gitea-values.yaml create mode 100644 infra/values/upc-dev/opencost-values.yaml create mode 100644 infra/values/upc-prod/gitea-values.yaml create mode 100644 infra/values/upc-prod/opencost-values.yaml create mode 100755 scripts/gitea-backup-aws.sh create mode 100755 scripts/gitea-backup-azure.sh create mode 100755 scripts/gitea-backup-gcp.sh diff --git a/README.md b/README.md index d419e33..c9511e0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Kubernetes Cluster - GitOps Configuration -> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for UpCloud Managed Kubernetes +> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for multi-cloud Kubernetes (UpCloud, AWS EKS, Azure AKS, GCP GKE) [![GitOps](https://img.shields.io/badge/GitOps-ArgoCD-blue)](https://argoproj.github.io/cd/) -[![Kubernetes](https://img.shields.io/badge/Kubernetes-UpCloud-orange)](https://upcloud.com/) +[![Kubernetes](https://img.shields.io/badge/Kubernetes-Multi--Cloud-orange)]() --- @@ -95,14 +95,26 @@ This repository contains the complete GitOps configuration for our Kubernetes cl │ │ ├── renovate.yaml │ │ ├── ... # All other Application manifests │ │ └── secrets.yaml -│ ├── overlays/ # Per-cluster overrides -│ │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is) -│ │ └── upc-prod/ # UpCloud Prod cluster (patches value paths) +│ ├── overlays/ # Per-cluster overrides (Kustomize) +│ │ ├── upc-dev/ # UpCloud Dev (uses base as-is) +│ │ ├── upc-prod/ # UpCloud Prod (patches value paths) +│ │ ├── aws-dev/ # AWS EKS Dev +│ │ ├── aws-prod/ # AWS EKS Prod +│ │ ├── azure-dev/ # Azure AKS Dev +│ │ ├── azure-prod/ # Azure AKS Prod +│ │ ├── gcp-dev/ # GCP GKE Dev +│ │ └── gcp-prod/ # GCP GKE Prod │ ├── dashboards/ # Grafana dashboard ConfigMaps │ └── values/ # Helm value overrides -│ ├── base/ # Shared values (all clusters) -│ ├── upc-dev/ # UpCloud Dev-specific values -│ └── upc-prod/ # UpCloud Prod-specific values +│ ├── base/ # Shared cloud-agnostic values +│ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing) +│ ├── upc-prod/ # UpCloud Prod +│ ├── aws-dev/ # AWS EKS Dev +│ ├── aws-prod/ # AWS EKS Prod +│ ├── azure-dev/ # Azure AKS Dev +│ ├── azure-prod/ # Azure AKS Prod +│ ├── gcp-dev/ # GCP GKE Dev +│ └── gcp-prod/ # GCP GKE Prod │ ├── apps/ # Business Applications │ ├── mcp10x.yaml @@ -361,7 +373,7 @@ kubectl patch application myapp -n argocd \ ## 📖 Key Concepts ### App-of-Apps Pattern -`_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{upc-dev,upc-prod}/` render the base Applications with per-cluster patches (e.g., swapping value file paths from `upc-dev` to `upc-prod`). +`_app-of-apps-{cluster}.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{cluster}/` render the base Applications with per-cluster patches (e.g., swapping value file paths). Supported clusters: `upc-dev`, `upc-prod`, `aws-dev`, `aws-prod`, `azure-dev`, `azure-prod`, `gcp-dev`, `gcp-prod`. ### Multi-Source Pattern Applications reference both: @@ -458,16 +470,14 @@ Documentation lives in `docs/`. To update: ## 📝 Notes ### Current Environment -- **Provider**: UpCloud Managed Kubernetes +- **Provider**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) +- **Active clusters**: UpCloud (upc-dev, upc-prod) - **Environment**: Production (internal use only) -- **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays - **Auth**: Disabled for ArgoCD (internal access) -- **Backup**: None (cluster rebuildable via GitOps) +- **Backup**: Gitea daily backup to S3-compatible storage ### Known Limitations -- No automated backups (yet) - Secret rotation not automated -- Multi-cluster limited to upc-dev and upc-prod environments - DNS management is manual **Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery) @@ -504,7 +514,7 @@ Internal use only. Not for public distribution. --- -**Last Updated**: 2026-03-16 +**Last Updated**: 2026-04-22 **Documentation Version**: 1.0.0 **🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!** diff --git a/_app-of-apps-aws-dev.yaml b/_app-of-apps-aws-dev.yaml new file mode 100644 index 0000000..061d19b --- /dev/null +++ b/_app-of-apps-aws-dev.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + annotations: + argocd.argoproj.io/sync-wave: "-1" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure-apps + namespace: argocd + labels: + app.kubernetes.io/name: infrastructure-apps + app.kubernetes.io/part-of: platform + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/overlays/aws-dev + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/_app-of-apps-aws-prod.yaml b/_app-of-apps-aws-prod.yaml new file mode 100644 index 0000000..62fd689 --- /dev/null +++ b/_app-of-apps-aws-prod.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + annotations: + argocd.argoproj.io/sync-wave: "-1" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure-apps + namespace: argocd + labels: + app.kubernetes.io/name: infrastructure-apps + app.kubernetes.io/part-of: platform + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/overlays/aws-prod + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/_app-of-apps-azure-dev.yaml b/_app-of-apps-azure-dev.yaml new file mode 100644 index 0000000..deeaefa --- /dev/null +++ b/_app-of-apps-azure-dev.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + annotations: + argocd.argoproj.io/sync-wave: "-1" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure-apps + namespace: argocd + labels: + app.kubernetes.io/name: infrastructure-apps + app.kubernetes.io/part-of: platform + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/overlays/azure-dev + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/_app-of-apps-azure-prod.yaml b/_app-of-apps-azure-prod.yaml new file mode 100644 index 0000000..9794896 --- /dev/null +++ b/_app-of-apps-azure-prod.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + annotations: + argocd.argoproj.io/sync-wave: "-1" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure-apps + namespace: argocd + labels: + app.kubernetes.io/name: infrastructure-apps + app.kubernetes.io/part-of: platform + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/overlays/azure-prod + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/_app-of-apps-gcp-dev.yaml b/_app-of-apps-gcp-dev.yaml new file mode 100644 index 0000000..63843ce --- /dev/null +++ b/_app-of-apps-gcp-dev.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + annotations: + argocd.argoproj.io/sync-wave: "-1" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure-apps + namespace: argocd + labels: + app.kubernetes.io/name: infrastructure-apps + app.kubernetes.io/part-of: platform + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/overlays/gcp-dev + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/_app-of-apps-gcp-prod.yaml b/_app-of-apps-gcp-prod.yaml new file mode 100644 index 0000000..32ae05f --- /dev/null +++ b/_app-of-apps-gcp-prod.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + annotations: + argocd.argoproj.io/sync-wave: "-1" +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure-apps + namespace: argocd + labels: + app.kubernetes.io/name: infrastructure-apps + app.kubernetes.io/part-of: platform + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: git@github.com:fortedigital/sturdy-adventure.git + targetRevision: HEAD + path: infra/overlays/gcp-prod + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/cluster-resources/gitea-backup-cronjob.yaml b/cluster-resources/gitea-backup-cronjob.yaml index d05ec17..e8a6fa4 100644 --- a/cluster-resources/gitea-backup-cronjob.yaml +++ b/cluster-resources/gitea-backup-cronjob.yaml @@ -57,17 +57,17 @@ spec: - sh - -c - | - mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}" + mc alias set s3 "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}" TIMESTAMP=$(date +%Y%m%d-%H%M%S) KEY="gitea-dump-${TIMESTAMP}.zip" echo "Uploading ${KEY}..." - mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \ + mc cp /backup/gitea-dump.zip "s3/${S3_BUCKET}/${KEY}" && \ echo "Upload complete." # Prune backups older than 7 days echo "Pruning backups older than 7 days..." - mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true + mc rm --older-than 7d --force "s3/${S3_BUCKET}/" 2>&1 || true echo "Pruning complete." envFrom: - secretRef: diff --git a/clusters/aws-dev.yaml b/clusters/aws-dev.yaml new file mode 100644 index 0000000..02f3034 --- /dev/null +++ b/clusters/aws-dev.yaml @@ -0,0 +1,10 @@ +clusterName: dev-eks # <- adjust to your EKS cluster name +domain: example.com # <- adjust to your domain +argocdDomain: argocd.example.com +grafanaDomain: grafana.example.com +keycloakDomain: id.example.com +dotaiDomain: kubemcp.example.com +dotaiUiDomain: kubemcpui.example.com +letsencryptEmail: admin@example.com # <- adjust +trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR +cloudProvider: aws diff --git a/clusters/aws-prod.yaml b/clusters/aws-prod.yaml new file mode 100644 index 0000000..c5973f9 --- /dev/null +++ b/clusters/aws-prod.yaml @@ -0,0 +1,10 @@ +clusterName: prod-eks # <- adjust to your EKS cluster name +domain: example.com # <- adjust to your domain +argocdDomain: argocd.example.com +grafanaDomain: grafana.example.com +keycloakDomain: id.example.com +dotaiDomain: kubemcp.example.com +dotaiUiDomain: kubemcpui.example.com +letsencryptEmail: admin@example.com # <- adjust +trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR +cloudProvider: aws diff --git a/clusters/azure-dev.yaml b/clusters/azure-dev.yaml new file mode 100644 index 0000000..5a3ace2 --- /dev/null +++ b/clusters/azure-dev.yaml @@ -0,0 +1,10 @@ +clusterName: dev-aks # <- adjust to your AKS cluster name +domain: example.com # <- adjust to your domain +argocdDomain: argocd.example.com +grafanaDomain: grafana.example.com +keycloakDomain: id.example.com +dotaiDomain: kubemcp.example.com +dotaiUiDomain: kubemcpui.example.com +letsencryptEmail: admin@example.com # <- adjust +trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe +cloudProvider: azure diff --git a/clusters/azure-prod.yaml b/clusters/azure-prod.yaml new file mode 100644 index 0000000..0be858e --- /dev/null +++ b/clusters/azure-prod.yaml @@ -0,0 +1,10 @@ +clusterName: prod-aks # <- adjust to your AKS cluster name +domain: example.com # <- adjust to your domain +argocdDomain: argocd.example.com +grafanaDomain: grafana.example.com +keycloakDomain: id.example.com +dotaiDomain: kubemcp.example.com +dotaiUiDomain: kubemcpui.example.com +letsencryptEmail: admin@example.com # <- adjust +trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe +cloudProvider: azure diff --git a/clusters/gcp-dev.yaml b/clusters/gcp-dev.yaml new file mode 100644 index 0000000..43f3861 --- /dev/null +++ b/clusters/gcp-dev.yaml @@ -0,0 +1,10 @@ +clusterName: dev-gke # <- adjust to your GKE cluster name +domain: example.com # <- adjust to your domain +argocdDomain: argocd.example.com +grafanaDomain: grafana.example.com +keycloakDomain: id.example.com +dotaiDomain: kubemcp.example.com +dotaiUiDomain: kubemcpui.example.com +letsencryptEmail: admin@example.com # <- adjust +trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks +cloudProvider: gcp diff --git a/clusters/gcp-prod.yaml b/clusters/gcp-prod.yaml new file mode 100644 index 0000000..ec814f7 --- /dev/null +++ b/clusters/gcp-prod.yaml @@ -0,0 +1,10 @@ +clusterName: prod-gke # <- adjust to your GKE cluster name +domain: example.com # <- adjust to your domain +argocdDomain: argocd.example.com +grafanaDomain: grafana.example.com +keycloakDomain: id.example.com +dotaiDomain: kubemcp.example.com +dotaiUiDomain: kubemcpui.example.com +letsencryptEmail: admin@example.com # <- adjust +trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks +cloudProvider: gcp diff --git a/docs/GITOPS-ARCHITECTURE.md b/docs/GITOPS-ARCHITECTURE.md index ec9769f..a57fb4b 100644 --- a/docs/GITOPS-ARCHITECTURE.md +++ b/docs/GITOPS-ARCHITECTURE.md @@ -12,11 +12,11 @@ ## Overview -This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where Git repositories serve as the single source of truth for both infrastructure and application deployments. The cluster is running on **UpCloud Managed Kubernetes** but is designed to be cloud-agnostic. +This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where Git repositories serve as the single source of truth for both infrastructure and application deployments. The cluster setup is **cloud-agnostic**, with ready-to-use configurations for **UpCloud**, **AWS EKS**, **Azure AKS**, and **GCP GKE**. ### Key Characteristics - **Environment**: Production (internal use only) -- **Cluster Type**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays +- **Cluster Type**: Multi-cloud, multi-cluster via Kustomize overlays (UpCloud, AWS, Azure, GCP) - **GitOps Tool**: ArgoCD - **Deployment Pattern**: App-of-Apps - **Secret Management**: Sealed Secrets (kubeseal) @@ -63,7 +63,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where ▼ ┌────────────────────────────────┐ │ Kubernetes Clusters │ - │ (UpCloud: upc-dev, upc-prod) │ + │ (UpCloud, AWS, Azure, GCP) │ │ │ │ ┌──────────────────────────┐ │ │ │ ArgoCD │ │ @@ -131,26 +131,22 @@ launchpad/ │ │ ├── renovate.yaml │ │ ├── ... # All other Application manifests │ │ └── secrets.yaml -│ ├── overlays/ # Per-cluster overrides +│ ├── overlays/ # Per-cluster Kustomize overrides │ │ ├── upc-dev/ # UpCloud Dev (uses base as-is) -│ │ └── upc-prod/ # UpCloud Prod (patches value paths) +│ │ ├── upc-prod/ # UpCloud Prod (patches value paths) +│ │ ├── aws-dev/ # AWS EKS Dev +│ │ ├── aws-prod/ # AWS EKS Prod +│ │ ├── azure-dev/ # Azure AKS Dev +│ │ ├── azure-prod/ # Azure AKS Prod +│ │ ├── gcp-dev/ # GCP GKE Dev +│ │ └── gcp-prod/ # GCP GKE Prod │ ├── dashboards/ # Grafana dashboard ConfigMaps │ └── values/ # Helm value overrides for infra -│ ├── base/ # Shared values (all clusters) -│ │ ├── traefik-values.yaml -│ │ ├── keycloak-values.yaml -│ │ ├── grafana-values.yaml -│ │ ├── prometheus-values.yaml -│ │ ├── gitea-values.yaml -│ │ └── ... -│ ├── upc-dev/ # upc-dev cluster-specific values -│ │ ├── traefik-values.yaml -│ │ ├── keycloak-values.yaml -│ │ └── grafana-values.yaml -│ └── upc-prod/ # upc-prod cluster-specific values -│ ├── traefik-values.yaml -│ ├── keycloak-values.yaml -│ └── grafana-values.yaml +│ ├── base/ # Cloud-agnostic shared values +│ ├── upc-{dev,prod}/ # UpCloud: storage class, LB, pricing +│ ├── aws-{dev,prod}/ # AWS: gp3, NLB, CUR pricing +│ ├── azure-{dev,prod}/ # Azure: managed-csi-premium, Standard LB +│ └── gcp-{dev,prod}/ # GCP: premium-rwo, L4 LB │ ├── apps/ # Business Application ArgoCD manifests (Kustomize) │ ├── base/ # Base app manifests @@ -287,7 +283,7 @@ app-repository/ ### The App-of-Apps Pattern ``` -_app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster) +_app-of-apps-{cluster}.yaml (Root, per cluster — e.g. upc-dev, aws-prod, gcp-dev) │ ├── infrastructure-apps (manages infra/) │ ├── cluster-resources-application @@ -377,6 +373,15 @@ patches: value: $values/infra/values/upc-prod/traefik-values.yaml ``` +Cloud-specific values (storage classes, load balancer annotations, cost model) are isolated in per-cluster value files. Base values are fully cloud-agnostic: + +| Cloud | Storage Class | Load Balancer | OpenCost Provider | +|-------|--------------|---------------|-------------------| +| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud LB (ProxyProtocol v2) | Custom pricing | +| **AWS EKS** | `gp3` (EBS CSI) | NLB (ProxyProtocol v2) | AWS CUR | +| **Azure AKS** | `managed-csi-premium` | Standard LB (`externalTrafficPolicy: Local`) | Azure Billing API | +| **GCP GKE** | `premium-rwo` (PD CSI) | L4 passthrough NLB | GCP Cloud Billing | + **Benefits**: - Single source of truth for Application definitions - Cluster-specific values isolated per overlay @@ -658,6 +663,6 @@ Notifications include: --- -**Last Updated**: 2026-03-16 +**Last Updated**: 2026-04-22 **Maintained By**: Platform Team **Questions?**: Contact #platform-support on Slack diff --git a/docs/OPERATIONS-RUNBOOK.md b/docs/OPERATIONS-RUNBOOK.md index a02a239..03ea097 100644 --- a/docs/OPERATIONS-RUNBOOK.md +++ b/docs/OPERATIONS-RUNBOOK.md @@ -37,7 +37,7 @@ Bootstrap a new cluster from scratch: #### Prerequisites -1. **Kubernetes cluster running** (UpCloud or any K8s cluster) +1. **Kubernetes cluster running** (UpCloud, AWS EKS, Azure AKS, GCP GKE, or any K8s cluster) 2. **kubectl configured** with admin access 3. **Repositories cloned** locally @@ -54,11 +54,13 @@ kubectl get nodes git clone https://git.forteapps.net/Forte/launchpad cd launchpad -# 2. Set cluster name (optional) -export CLUSTER_NAME="prod-cluster-01" +# 2. Run bootstrap script with cluster target +# Available clusters: upc-dev, upc-prod, aws-dev, aws-prod, +# azure-dev, azure-prod, gcp-dev, gcp-prod +./bootstrap.sh upc-dev -# 3. Run bootstrap script -./bootstrap.sh +# Cluster config is loaded from clusters/.yaml +# (cloudProvider, trustedIPs, domain, etc.) ``` **What Happens:** @@ -1262,13 +1264,21 @@ spec: ### Backup Strategy -**Current State**: No automated backups +**Current State**: Gitea daily backups to S3-compatible storage -**What Needs Backup**: -- ❌ Cluster state (not backed up - recreate via GitOps) -- ❌ Persistent volumes (currently not critical) -- ✅ Git repositories (Gitea provides backup) -- ⚠️ Secrets (sealed secrets in Git, unseal keys need safekeeping) +**What Is Backed Up**: +- ✅ Gitea repositories + database: Daily CronJob (`cluster-resources/gitea-backup-cronjob.yaml`) uploads to S3-compatible storage with 7-day retention +- ✅ Git repositories: Full cluster config recoverable from Git +- ⚠️ Secrets: Sealed secrets in Git; unseal keys need safekeeping + +**What Is NOT Backed Up**: +- ❌ Cluster state (recreate via GitOps) +- ❌ Other persistent volumes (Prometheus, Loki, Tempo data) + +**Per-cloud backup scripts** (manual restore helpers): +- UpCloud/AWS: `scripts/gitea-backup.sh` / `scripts/gitea-backup-aws.sh` (MinIO CLI, S3-compatible) +- Azure: `scripts/gitea-backup-azure.sh` (Azure CLI + Blob Storage) +- GCP: `scripts/gitea-backup-gcp.sh` (gsutil + GCS) ### Cluster Rebuild @@ -1370,6 +1380,9 @@ kubectl get pods -n argocd ```bash # UpCloud: Upgrade via control panel or CLI +# AWS EKS: eksctl upgrade cluster / AWS Console +# Azure AKS: az aks upgrade / Azure Portal +# GCP GKE: gcloud container clusters upgrade / Cloud Console # After upgrade, verify cluster kubectl version @@ -1507,18 +1520,35 @@ git push ### Multi-Cluster Setup -The repository supports multiple clusters via Kustomize overlays: +The repository supports multiple clusters across multiple clouds via Kustomize overlays: +**Active clusters:** - **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is - **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod` -Each cluster has its own: -- Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml` -- Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/` -- Sealed secrets: `secrets/upc-dev/` (others as needed) -- Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/` +**Cloud-ready templates (fill in `clusters/*.yaml` before use):** +- **aws-dev** / **aws-prod**: AWS EKS with NLB, gp3 storage, AWS CUR pricing +- **azure-dev** / **azure-prod**: Azure AKS with Standard LB, managed-csi-premium storage +- **gcp-dev** / **gcp-prod**: GCP GKE with L4 LB, premium-rwo storage -To add a new cluster, create a new overlay directory (e.g., `infra/overlays/upc-staging/`) with patches that swap the value file paths. +Each cluster has its own: +- Root app-of-apps: `_app-of-apps-{cluster}.yaml` +- Cluster config: `clusters/{cluster}.yaml` (domain, trustedIPs, cloudProvider) +- Kustomize overlay: `infra/overlays/{cluster}/kustomization.yaml` +- Helm value overrides: `infra/values/{cluster}/` (traefik, gitea, opencost) +- Sealed secrets: `secrets/{cluster}/` (as needed) +- Apps overlay: `apps/overlays/{cluster}/` + +Cloud-specific values handled per-cluster: + +| Concern | UpCloud | AWS EKS | Azure AKS | GCP GKE | +|---------|---------|---------|-----------|---------| +| **Storage class** | `upcloud-block-storage-maxiops` | `gp3` | `managed-csi-premium` | `premium-rwo` | +| **Load balancer** | UpCloud LB + ProxyProtocol v2 | NLB + ProxyProtocol v2 | Standard LB + `externalTrafficPolicy: Local` | L4 passthrough NLB | +| **Cost monitoring** | Custom pricing | AWS CUR | Azure Billing API | GCP Cloud Billing | +| **Backup storage** | UpCloud S3-compat | AWS S3 (native) | Azure Blob Storage | GCS | + +To add a new cluster, create a new overlay directory (e.g., `infra/overlays/aws-staging/`) with patches that swap the value file paths, and a matching `clusters/aws-staging.yaml`. ### Blue-Green Deployments @@ -1661,6 +1691,6 @@ echo "Remember to delete: $SECRET_FILE" --- -**Last Updated**: 2026-03-16 +**Last Updated**: 2026-04-22 **Maintained By**: Platform Team **Emergency Contact**: #platform-support on Slack diff --git a/docs/README.md b/docs/README.md index 47a394e..6b48500 100644 --- a/docs/README.md +++ b/docs/README.md @@ -180,7 +180,7 @@ Reference for: │ ▼ ┌──────────────────────────────────────────────────────────────┐ -│ Kubernetes Clusters (UpCloud: upc-dev, upc-prod) │ +│ Kubernetes Clusters (UpCloud, AWS, Azure, GCP) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │ │ ├──────────────────────────────────────────────────────┤ │ @@ -194,7 +194,7 @@ Reference for: ### Key Technologies - **GitOps**: ArgoCD -- **Kubernetes**: UpCloud Managed Kubernetes (multi-cluster: upc-dev, upc-prod) +- **Kubernetes**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) - **Ingress**: Traefik v2 - **Certificates**: Cert-Manager + Let's Encrypt - **Policies**: Kyverno @@ -299,11 +299,16 @@ docs/ ## 🔄 Documentation Versions **Current Version**: 1.0.0 -**Last Updated**: 2026-03-16 +**Last Updated**: 2026-04-22 **Maintained By**: Platform Team ### Changelog +- **v1.1.0 (2026-04-22)**: Multi-cloud support + - Cloud-agnostic base values (storage, LB, pricing moved to per-cluster overlays) + - Added AWS EKS, Azure AKS, GCP GKE configurations + - Per-cloud backup scripts + - Updated all documentation - **v1.0.0 (2026-03-16)**: Initial comprehensive documentation release - GitOps Architecture guide - Developer Onboarding guide diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 5ba17aa..f5be8f5 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -19,9 +19,9 @@ | Component | Value | |-----------|-------| -| **Provider** | UpCloud Managed Kubernetes | -| **Environment** | Production (internal use) | -| **Cluster Count** | Multi-cluster (upc-dev, upc-prod) | +| **Provider** | Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) | +| **Active clusters** | UpCloud (upc-dev, upc-prod) | +| **Cloud-ready templates** | AWS, Azure, GCP (dev + prod each) | | **GitOps Tool** | ArgoCD | | **Ingress Controller** | Traefik v2 | | **Certificate Management** | Cert-Manager + Let's Encrypt | @@ -42,7 +42,7 @@ Internet [DNS: *.forteapps.net] │ ▼ -[UpCloud LoadBalancer] +[Cloud Load Balancer] │ ▼ [Traefik Ingress Controller] @@ -1470,14 +1470,22 @@ Recommended resource allocation: ### Storage Classes -Default storage class used: **UpCloud default** (varies by provider) +Storage classes are cloud-specific and configured in per-cluster value overrides (`infra/values/{cluster}/gitea-values.yaml`): + +| Cloud | Storage Class | Driver | +|-------|--------------|--------| +| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud CSI | +| **AWS EKS** | `gp3` | EBS CSI | +| **Azure AKS** | `managed-csi-premium` | Azure Disk CSI | +| **GCP GKE** | `premium-rwo` | PD CSI | ```yaml +# Example: base values omit storageClass (set in per-cluster overlay) persistence: enabled: true - storageClass: "" # Uses default accessMode: ReadWriteOnce size: 5Gi + # storageClass set by infra/values/{cluster}/gitea-values.yaml ``` --- @@ -1673,6 +1681,6 @@ team: platform --- -**Last Updated**: 2026-04-16 +**Last Updated**: 2026-04-22 **Maintained By**: Platform Team **Version**: 1.0.0 diff --git a/infra/base/gitea.yaml b/infra/base/gitea.yaml index ba806f5..cc4f60f 100644 --- a/infra/base/gitea.yaml +++ b/infra/base/gitea.yaml @@ -22,6 +22,7 @@ spec: releaseName: gitea valueFiles: - $values/infra/values/base/gitea-values.yaml + - $values/infra/values/upc-dev/gitea-values.yaml - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git targetRevision: HEAD diff --git a/infra/base/opencost.yaml b/infra/base/opencost.yaml index 6984f3b..a102906 100644 --- a/infra/base/opencost.yaml +++ b/infra/base/opencost.yaml @@ -22,6 +22,7 @@ spec: releaseName: opencost valueFiles: - $values/infra/values/base/opencost-values.yaml + - $values/infra/values/upc-dev/opencost-values.yaml - repoURL: git@github.com:fortedigital/sturdy-adventure.git targetRevision: HEAD diff --git a/infra/overlays/aws-dev/kustomization.yaml b/infra/overlays/aws-dev/kustomization.yaml new file mode 100644 index 0000000..4be71fc --- /dev/null +++ b/infra/overlays/aws-dev/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base + +patches: +# Traefik: swap upc-dev → aws-dev +- target: + kind: Application + name: traefik + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/aws-dev/traefik-values.yaml + +# Gitea: swap upc-dev → aws-dev +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/aws-dev/gitea-values.yaml + +# OpenCost: swap upc-dev → aws-dev +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/aws-dev/opencost-values.yaml + +# TODO: add patches for keycloak, grafana, secrets, enterprise-apps +# when deploying to this cluster (these are deployment-specific, not cloud-specific) diff --git a/infra/overlays/aws-prod/kustomization.yaml b/infra/overlays/aws-prod/kustomization.yaml new file mode 100644 index 0000000..ce22faf --- /dev/null +++ b/infra/overlays/aws-prod/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base + +patches: +# Traefik: swap upc-dev → aws-prod +- target: + kind: Application + name: traefik + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/aws-prod/traefik-values.yaml + +# Gitea: swap upc-dev → aws-prod +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/aws-prod/gitea-values.yaml + +# OpenCost: swap upc-dev → aws-prod +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/aws-prod/opencost-values.yaml + +# TODO: add patches for keycloak, grafana, secrets, enterprise-apps +# when deploying to this cluster (these are deployment-specific, not cloud-specific) diff --git a/infra/overlays/azure-dev/kustomization.yaml b/infra/overlays/azure-dev/kustomization.yaml new file mode 100644 index 0000000..d7a014d --- /dev/null +++ b/infra/overlays/azure-dev/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base + +patches: +# Traefik: swap upc-dev → azure-dev +- target: + kind: Application + name: traefik + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/azure-dev/traefik-values.yaml + +# Gitea: swap upc-dev → azure-dev +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/azure-dev/gitea-values.yaml + +# OpenCost: swap upc-dev → azure-dev +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/azure-dev/opencost-values.yaml + +# TODO: add patches for keycloak, grafana, secrets, enterprise-apps +# when deploying to this cluster (these are deployment-specific, not cloud-specific) diff --git a/infra/overlays/azure-prod/kustomization.yaml b/infra/overlays/azure-prod/kustomization.yaml new file mode 100644 index 0000000..4a9d6cf --- /dev/null +++ b/infra/overlays/azure-prod/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base + +patches: +# Traefik: swap upc-dev → azure-prod +- target: + kind: Application + name: traefik + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/azure-prod/traefik-values.yaml + +# Gitea: swap upc-dev → azure-prod +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/azure-prod/gitea-values.yaml + +# OpenCost: swap upc-dev → azure-prod +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/azure-prod/opencost-values.yaml + +# TODO: add patches for keycloak, grafana, secrets, enterprise-apps +# when deploying to this cluster (these are deployment-specific, not cloud-specific) diff --git a/infra/overlays/gcp-dev/kustomization.yaml b/infra/overlays/gcp-dev/kustomization.yaml new file mode 100644 index 0000000..491065e --- /dev/null +++ b/infra/overlays/gcp-dev/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base + +patches: +# Traefik: swap upc-dev → gcp-dev +- target: + kind: Application + name: traefik + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/gcp-dev/traefik-values.yaml + +# Gitea: swap upc-dev → gcp-dev +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/gcp-dev/gitea-values.yaml + +# OpenCost: swap upc-dev → gcp-dev +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/gcp-dev/opencost-values.yaml + +# TODO: add patches for keycloak, grafana, secrets, enterprise-apps +# when deploying to this cluster (these are deployment-specific, not cloud-specific) diff --git a/infra/overlays/gcp-prod/kustomization.yaml b/infra/overlays/gcp-prod/kustomization.yaml new file mode 100644 index 0000000..9971aa9 --- /dev/null +++ b/infra/overlays/gcp-prod/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ../../base + +patches: +# Traefik: swap upc-dev → gcp-prod +- target: + kind: Application + name: traefik + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/gcp-prod/traefik-values.yaml + +# Gitea: swap upc-dev → gcp-prod +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/gcp-prod/gitea-values.yaml + +# OpenCost: swap upc-dev → gcp-prod +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/gcp-prod/opencost-values.yaml + +# TODO: add patches for keycloak, grafana, secrets, enterprise-apps +# when deploying to this cluster (these are deployment-specific, not cloud-specific) diff --git a/infra/overlays/upc-prod/kustomization.yaml b/infra/overlays/upc-prod/kustomization.yaml index ebfc179..5a6c53d 100644 --- a/infra/overlays/upc-prod/kustomization.yaml +++ b/infra/overlays/upc-prod/kustomization.yaml @@ -48,3 +48,21 @@ patches: - op: replace path: /spec/source/path value: apps/overlays/upc-prod + +# Gitea: swap upc-dev → upc-prod +- target: + kind: Application + name: gitea + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/upc-prod/gitea-values.yaml + +# OpenCost: swap upc-dev → upc-prod +- target: + kind: Application + name: opencost + patch: | + - op: replace + path: /spec/sources/0/helm/valueFiles/1 + value: $values/infra/values/upc-prod/opencost-values.yaml diff --git a/infra/values/aws-dev/gitea-values.yaml b/infra/values/aws-dev/gitea-values.yaml new file mode 100644 index 0000000..597af4f --- /dev/null +++ b/infra/values/aws-dev/gitea-values.yaml @@ -0,0 +1,7 @@ +# AWS EBS gp3 storage class (requires EBS CSI driver) +persistence: + storageClass: gp3 +postgresql: + primary: + persistence: + storageClass: gp3 diff --git a/infra/values/aws-dev/opencost-values.yaml b/infra/values/aws-dev/opencost-values.yaml new file mode 100644 index 0000000..93ff67a --- /dev/null +++ b/infra/values/aws-dev/opencost-values.yaml @@ -0,0 +1,13 @@ +# AWS native pricing via Cost and Usage Reports +opencost: + exporter: + customPricing: + enabled: true + provider: aws + aws: + service_key_name: "" # <- populate or use IRSA + service_key_secret: "" + spot_data_region: "" + spot_data_bucket: "" + spot_data_prefix: "" + account_id: "" diff --git a/infra/values/aws-dev/traefik-values.yaml b/infra/values/aws-dev/traefik-values.yaml new file mode 100644 index 0000000..34306f7 --- /dev/null +++ b/infra/values/aws-dev/traefik-values.yaml @@ -0,0 +1,18 @@ +# AWS EKS — NLB with Proxy Protocol v2 for real client IPs +service: + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "external" + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" + service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" + service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" +ports: + web: + proxyProtocol: + trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR + forwardedHeaders: + trustedIPs: "10.0.0.0/8" + websecure: + proxyProtocol: + trustedIPs: "10.0.0.0/8" + forwardedHeaders: + trustedIPs: "10.0.0.0/8" diff --git a/infra/values/aws-prod/gitea-values.yaml b/infra/values/aws-prod/gitea-values.yaml new file mode 100644 index 0000000..597af4f --- /dev/null +++ b/infra/values/aws-prod/gitea-values.yaml @@ -0,0 +1,7 @@ +# AWS EBS gp3 storage class (requires EBS CSI driver) +persistence: + storageClass: gp3 +postgresql: + primary: + persistence: + storageClass: gp3 diff --git a/infra/values/aws-prod/opencost-values.yaml b/infra/values/aws-prod/opencost-values.yaml new file mode 100644 index 0000000..93ff67a --- /dev/null +++ b/infra/values/aws-prod/opencost-values.yaml @@ -0,0 +1,13 @@ +# AWS native pricing via Cost and Usage Reports +opencost: + exporter: + customPricing: + enabled: true + provider: aws + aws: + service_key_name: "" # <- populate or use IRSA + service_key_secret: "" + spot_data_region: "" + spot_data_bucket: "" + spot_data_prefix: "" + account_id: "" diff --git a/infra/values/aws-prod/traefik-values.yaml b/infra/values/aws-prod/traefik-values.yaml new file mode 100644 index 0000000..34306f7 --- /dev/null +++ b/infra/values/aws-prod/traefik-values.yaml @@ -0,0 +1,18 @@ +# AWS EKS — NLB with Proxy Protocol v2 for real client IPs +service: + annotations: + service.beta.kubernetes.io/aws-load-balancer-type: "external" + service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip" + service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing" + service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" +ports: + web: + proxyProtocol: + trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR + forwardedHeaders: + trustedIPs: "10.0.0.0/8" + websecure: + proxyProtocol: + trustedIPs: "10.0.0.0/8" + forwardedHeaders: + trustedIPs: "10.0.0.0/8" diff --git a/infra/values/azure-dev/gitea-values.yaml b/infra/values/azure-dev/gitea-values.yaml new file mode 100644 index 0000000..5bb20ff --- /dev/null +++ b/infra/values/azure-dev/gitea-values.yaml @@ -0,0 +1,7 @@ +# Azure Managed Disk (Premium SSD via CSI driver) +persistence: + storageClass: managed-csi-premium +postgresql: + primary: + persistence: + storageClass: managed-csi-premium diff --git a/infra/values/azure-dev/opencost-values.yaml b/infra/values/azure-dev/opencost-values.yaml new file mode 100644 index 0000000..98b30cd --- /dev/null +++ b/infra/values/azure-dev/opencost-values.yaml @@ -0,0 +1,11 @@ +# Azure native pricing via Billing API +opencost: + exporter: + customPricing: + enabled: true + provider: azure + azure: + subscriptionID: "" # <- populate + clientID: "" + clientSecret: "" + tenantID: "" diff --git a/infra/values/azure-dev/traefik-values.yaml b/infra/values/azure-dev/traefik-values.yaml new file mode 100644 index 0000000..7efa198 --- /dev/null +++ b/infra/values/azure-dev/traefik-values.yaml @@ -0,0 +1,16 @@ +# Azure AKS — Standard Load Balancer +# Note: Azure Standard LB does not support Proxy Protocol. +# Use externalTrafficPolicy: Local on the Traefik service to preserve +# client IPs, or deploy behind Azure Application Gateway. +service: + annotations: + service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/ping" + spec: + externalTrafficPolicy: Local +ports: + web: + forwardedHeaders: + trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe + websecure: + forwardedHeaders: + trustedIPs: "10.0.0.0/8,168.63.129.16/32" diff --git a/infra/values/azure-prod/gitea-values.yaml b/infra/values/azure-prod/gitea-values.yaml new file mode 100644 index 0000000..5bb20ff --- /dev/null +++ b/infra/values/azure-prod/gitea-values.yaml @@ -0,0 +1,7 @@ +# Azure Managed Disk (Premium SSD via CSI driver) +persistence: + storageClass: managed-csi-premium +postgresql: + primary: + persistence: + storageClass: managed-csi-premium diff --git a/infra/values/azure-prod/opencost-values.yaml b/infra/values/azure-prod/opencost-values.yaml new file mode 100644 index 0000000..98b30cd --- /dev/null +++ b/infra/values/azure-prod/opencost-values.yaml @@ -0,0 +1,11 @@ +# Azure native pricing via Billing API +opencost: + exporter: + customPricing: + enabled: true + provider: azure + azure: + subscriptionID: "" # <- populate + clientID: "" + clientSecret: "" + tenantID: "" diff --git a/infra/values/azure-prod/traefik-values.yaml b/infra/values/azure-prod/traefik-values.yaml new file mode 100644 index 0000000..7efa198 --- /dev/null +++ b/infra/values/azure-prod/traefik-values.yaml @@ -0,0 +1,16 @@ +# Azure AKS — Standard Load Balancer +# Note: Azure Standard LB does not support Proxy Protocol. +# Use externalTrafficPolicy: Local on the Traefik service to preserve +# client IPs, or deploy behind Azure Application Gateway. +service: + annotations: + service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/ping" + spec: + externalTrafficPolicy: Local +ports: + web: + forwardedHeaders: + trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe + websecure: + forwardedHeaders: + trustedIPs: "10.0.0.0/8,168.63.129.16/32" diff --git a/infra/values/base/gitea-values.yaml b/infra/values/base/gitea-values.yaml index e34f256..75c2e72 100644 --- a/infra/values/base/gitea-values.yaml +++ b/infra/values/base/gitea-values.yaml @@ -127,7 +127,6 @@ persistence: size: 10Gi accessModes: - ReadWriteOnce - storageClass: upcloud-block-storage-maxiops # -- Recreate strategy to avoid Multi-Attach errors with RWO volumes strategy: @@ -153,7 +152,6 @@ postgresql: persistence: enabled: true size: 8Gi - storageClass: upcloud-block-storage-maxiops resources: requests: cpu: 100m diff --git a/infra/values/base/opencost-values.yaml b/infra/values/base/opencost-values.yaml index 39d73cc..dde13fb 100644 --- a/infra/values/base/opencost-values.yaml +++ b/infra/values/base/opencost-values.yaml @@ -10,18 +10,8 @@ opencost: serviceName: prometheus-server namespaceName: monitoring port: 80 - customPricing: - enabled: true - provider: custom - costModel: - description: "UpCloud 4-node cluster pricing" - CPU: "5.86" - RAM: "1.46" - GPU: "0" - storage: "0.34" - zoneNetworkEgress: "0" - regionNetworkEgress: "0" - internetNetworkEgress: "0" + # Cloud-specific pricing is in per-cluster value overrides + # (e.g. infra/values/upc-dev/opencost-values.yaml) ui: enabled: false service: diff --git a/infra/values/gcp-dev/gitea-values.yaml b/infra/values/gcp-dev/gitea-values.yaml new file mode 100644 index 0000000..b825aee --- /dev/null +++ b/infra/values/gcp-dev/gitea-values.yaml @@ -0,0 +1,7 @@ +# GCP Persistent Disk (SSD via CSI driver) +persistence: + storageClass: premium-rwo +postgresql: + primary: + persistence: + storageClass: premium-rwo diff --git a/infra/values/gcp-dev/opencost-values.yaml b/infra/values/gcp-dev/opencost-values.yaml new file mode 100644 index 0000000..f3ea481 --- /dev/null +++ b/infra/values/gcp-dev/opencost-values.yaml @@ -0,0 +1,9 @@ +# GCP native pricing via Cloud Billing API +opencost: + exporter: + customPricing: + enabled: true + provider: gcp + gcp: + projectID: "" # <- populate with your GCP project ID + key: "" # <- or use Workload Identity diff --git a/infra/values/gcp-dev/traefik-values.yaml b/infra/values/gcp-dev/traefik-values.yaml new file mode 100644 index 0000000..55351c0 --- /dev/null +++ b/infra/values/gcp-dev/traefik-values.yaml @@ -0,0 +1,15 @@ +# GCP GKE — External passthrough Network Load Balancer +service: + annotations: + cloud.google.com/l4-rbs: "enabled" +ports: + web: + proxyProtocol: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks + forwardedHeaders: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" + websecure: + proxyProtocol: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" + forwardedHeaders: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" diff --git a/infra/values/gcp-prod/gitea-values.yaml b/infra/values/gcp-prod/gitea-values.yaml new file mode 100644 index 0000000..b825aee --- /dev/null +++ b/infra/values/gcp-prod/gitea-values.yaml @@ -0,0 +1,7 @@ +# GCP Persistent Disk (SSD via CSI driver) +persistence: + storageClass: premium-rwo +postgresql: + primary: + persistence: + storageClass: premium-rwo diff --git a/infra/values/gcp-prod/opencost-values.yaml b/infra/values/gcp-prod/opencost-values.yaml new file mode 100644 index 0000000..f3ea481 --- /dev/null +++ b/infra/values/gcp-prod/opencost-values.yaml @@ -0,0 +1,9 @@ +# GCP native pricing via Cloud Billing API +opencost: + exporter: + customPricing: + enabled: true + provider: gcp + gcp: + projectID: "" # <- populate with your GCP project ID + key: "" # <- or use Workload Identity diff --git a/infra/values/gcp-prod/traefik-values.yaml b/infra/values/gcp-prod/traefik-values.yaml new file mode 100644 index 0000000..55351c0 --- /dev/null +++ b/infra/values/gcp-prod/traefik-values.yaml @@ -0,0 +1,15 @@ +# GCP GKE — External passthrough Network Load Balancer +service: + annotations: + cloud.google.com/l4-rbs: "enabled" +ports: + web: + proxyProtocol: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks + forwardedHeaders: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" + websecure: + proxyProtocol: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" + forwardedHeaders: + trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" diff --git a/infra/values/upc-dev/gitea-values.yaml b/infra/values/upc-dev/gitea-values.yaml new file mode 100644 index 0000000..ef1f8eb --- /dev/null +++ b/infra/values/upc-dev/gitea-values.yaml @@ -0,0 +1,7 @@ +# UpCloud storage class for Gitea and its embedded PostgreSQL +persistence: + storageClass: upcloud-block-storage-maxiops +postgresql: + primary: + persistence: + storageClass: upcloud-block-storage-maxiops diff --git a/infra/values/upc-dev/opencost-values.yaml b/infra/values/upc-dev/opencost-values.yaml new file mode 100644 index 0000000..06a7488 --- /dev/null +++ b/infra/values/upc-dev/opencost-values.yaml @@ -0,0 +1,15 @@ +# UpCloud custom pricing (no native OpenCost integration) +opencost: + exporter: + customPricing: + enabled: true + provider: custom + costModel: + description: "UpCloud 4-node cluster pricing" + CPU: "5.86" + RAM: "1.46" + GPU: "0" + storage: "0.34" + zoneNetworkEgress: "0" + regionNetworkEgress: "0" + internetNetworkEgress: "0" diff --git a/infra/values/upc-prod/gitea-values.yaml b/infra/values/upc-prod/gitea-values.yaml new file mode 100644 index 0000000..ef1f8eb --- /dev/null +++ b/infra/values/upc-prod/gitea-values.yaml @@ -0,0 +1,7 @@ +# UpCloud storage class for Gitea and its embedded PostgreSQL +persistence: + storageClass: upcloud-block-storage-maxiops +postgresql: + primary: + persistence: + storageClass: upcloud-block-storage-maxiops diff --git a/infra/values/upc-prod/opencost-values.yaml b/infra/values/upc-prod/opencost-values.yaml new file mode 100644 index 0000000..06a7488 --- /dev/null +++ b/infra/values/upc-prod/opencost-values.yaml @@ -0,0 +1,15 @@ +# UpCloud custom pricing (no native OpenCost integration) +opencost: + exporter: + customPricing: + enabled: true + provider: custom + costModel: + description: "UpCloud 4-node cluster pricing" + CPU: "5.86" + RAM: "1.46" + GPU: "0" + storage: "0.34" + zoneNetworkEgress: "0" + regionNetworkEgress: "0" + internetNetworkEgress: "0" diff --git a/scripts/gitea-backup-aws.sh b/scripts/gitea-backup-aws.sh new file mode 100755 index 0000000..ebf0894 --- /dev/null +++ b/scripts/gitea-backup-aws.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Gitea backup helper for AWS S3 +# Uses the gitea-backup-s3 secret in the gitea namespace +# (same secret schema: S3_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET) +# +# For AWS, S3_ENDPOINT is typically https://s3..amazonaws.com +# +# Usage: +# ./scripts/gitea-backup-aws.sh list # list all backups +# ./scripts/gitea-backup-aws.sh download # download a backup to current dir +# ./scripts/gitea-backup-aws.sh download latest # download the most recent backup + +NAMESPACE="gitea" +SECRET="gitea-backup-s3" +IMAGE="minio/mc:latest" +POD_NAME="gitea-backup-helper" +ALIAS_CMD='mc alias set s3 ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null' + +cleanup() { + kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true +} + +mc_run() { + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1 + kubectl -n "$NAMESPACE" logs "$POD_NAME" + cleanup +} + +case "${1:-help}" in + list) + echo "Listing backups..." + mc_run 'mc ls s3/${S3_BUCKET}/' + ;; + + download) + FILE="${2:?Usage: $0 download }" + + if [ "$FILE" = "latest" ]; then + echo "Finding latest backup..." + FILE=$(mc_run 'mc ls s3/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]') + if [ -z "$FILE" ]; then + echo "No backups found." + exit 1 + fi + echo "Latest: $FILE" + fi + + echo "Downloading $FILE..." + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"sleep 300\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1 + + echo "Saving to ./$FILE ..." + kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat s3/\${S3_BUCKET}/$FILE" > "./$FILE" + cleanup + + echo "Downloaded: ./$FILE" + ;; + + *) + echo "Gitea backup helper (AWS S3)" + echo "" + echo "Usage:" + echo " $0 list List all backups in S3" + echo " $0 download Download a specific backup" + echo " $0 download latest Download the most recent backup" + ;; +esac diff --git a/scripts/gitea-backup-azure.sh b/scripts/gitea-backup-azure.sh new file mode 100755 index 0000000..e2f14d7 --- /dev/null +++ b/scripts/gitea-backup-azure.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Gitea backup helper for Azure Blob Storage +# Uses the gitea-backup-azure secret in the gitea namespace +# Required secret keys: +# AZURE_STORAGE_ACCOUNT — storage account name +# AZURE_STORAGE_KEY — storage account key +# AZURE_CONTAINER — blob container name +# +# Usage: +# ./scripts/gitea-backup-azure.sh list # list all backups +# ./scripts/gitea-backup-azure.sh download # download a backup +# ./scripts/gitea-backup-azure.sh download latest # download the most recent backup + +NAMESPACE="gitea" +SECRET="gitea-backup-azure" +IMAGE="mcr.microsoft.com/azure-cli:latest" +POD_NAME="gitea-backup-helper" + +cleanup() { + kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true +} + +az_run() { + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"$1\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1 + kubectl -n "$NAMESPACE" logs "$POD_NAME" + cleanup +} + +case "${1:-help}" in + list) + echo "Listing backups..." + az_run 'az storage blob list --account-name ${AZURE_STORAGE_ACCOUNT} --account-key ${AZURE_STORAGE_KEY} --container-name ${AZURE_CONTAINER} --output table --query "[].{Name:name, Size:properties.contentLength, Modified:properties.lastModified}"' + ;; + + download) + FILE="${2:?Usage: $0 download }" + + if [ "$FILE" = "latest" ]; then + echo "Finding latest backup..." + FILE=$(az_run 'az storage blob list --account-name ${AZURE_STORAGE_ACCOUNT} --account-key ${AZURE_STORAGE_KEY} --container-name ${AZURE_CONTAINER} --query "sort_by([], &properties.lastModified)[-1].name" -o tsv' | tr -d '[:space:]') + if [ -z "$FILE" ]; then + echo "No backups found." + exit 1 + fi + echo "Latest: $FILE" + fi + + echo "Downloading $FILE..." + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"sleep 300\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1 + + echo "Saving to ./$FILE ..." + kubectl -n "$NAMESPACE" exec "$POD_NAME" -- \ + az storage blob download \ + --account-name "\${AZURE_STORAGE_ACCOUNT}" \ + --account-key "\${AZURE_STORAGE_KEY}" \ + --container-name "\${AZURE_CONTAINER}" \ + --name "$FILE" \ + --file /dev/stdout 2>/dev/null > "./$FILE" + cleanup + + echo "Downloaded: ./$FILE" + ;; + + *) + echo "Gitea backup helper (Azure Blob Storage)" + echo "" + echo "Usage:" + echo " $0 list List all backups in Azure Blob" + echo " $0 download Download a specific backup" + echo " $0 download latest Download the most recent backup" + ;; +esac diff --git a/scripts/gitea-backup-gcp.sh b/scripts/gitea-backup-gcp.sh new file mode 100755 index 0000000..54fb7ef --- /dev/null +++ b/scripts/gitea-backup-gcp.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Gitea backup helper for Google Cloud Storage +# Uses the gitea-backup-gcs secret in the gitea namespace +# Required secret keys: +# GCS_BUCKET — bucket name (without gs:// prefix) +# GOOGLE_APPLICATION_CREDENTIALS_JSON — service account key JSON +# (alternatively, use Workload Identity and omit the key) +# +# Usage: +# ./scripts/gitea-backup-gcp.sh list # list all backups +# ./scripts/gitea-backup-gcp.sh download # download a backup +# ./scripts/gitea-backup-gcp.sh download latest # download the most recent backup + +NAMESPACE="gitea" +SECRET="gitea-backup-gcs" +IMAGE="gcr.io/google.com/cloudsdktool/google-cloud-cli:slim" +POD_NAME="gitea-backup-helper" +AUTH_CMD='if [ -n "${GOOGLE_APPLICATION_CREDENTIALS_JSON:-}" ]; then echo "${GOOGLE_APPLICATION_CREDENTIALS_JSON}" > /tmp/gcs-key.json && gcloud auth activate-service-account --key-file=/tmp/gcs-key.json > /dev/null 2>&1; fi' + +cleanup() { + kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true +} + +gcs_run() { + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"${AUTH_CMD}; $1\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1 + kubectl -n "$NAMESPACE" logs "$POD_NAME" + cleanup +} + +case "${1:-help}" in + list) + echo "Listing backups..." + gcs_run 'gsutil ls -l gs://${GCS_BUCKET}/' + ;; + + download) + FILE="${2:?Usage: $0 download }" + + if [ "$FILE" = "latest" ]; then + echo "Finding latest backup..." + FILE=$(gcs_run 'gsutil ls gs://${GCS_BUCKET}/' | grep -v '^$' | grep -v 'TOTAL' | sort | tail -1 | xargs -I{} basename {} | tr -d '[:space:]') + if [ -z "$FILE" ]; then + echo "No backups found." + exit 1 + fi + echo "Latest: $FILE" + fi + + echo "Downloading $FILE..." + cleanup + kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \ + --image="$IMAGE" \ + --overrides="{ + \"spec\":{\"containers\":[{ + \"name\":\"$POD_NAME\", + \"image\":\"$IMAGE\", + \"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}], + \"command\":[\"sh\",\"-c\",\"sleep 300\"], + \"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}] + }]} + }" > /dev/null 2>&1 + + kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1 + + echo "Saving to ./$FILE ..." + kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${AUTH_CMD} && gsutil cat gs://\${GCS_BUCKET}/$FILE" > "./$FILE" + cleanup + + echo "Downloaded: ./$FILE" + ;; + + *) + echo "Gitea backup helper (Google Cloud Storage)" + echo "" + echo "Usage:" + echo " $0 list List all backups in GCS" + echo " $0 download Download a specific backup" + echo " $0 download latest Download the most recent backup" + ;; +esac