15 Commits

Author SHA1 Message Date
093e57c4cc sync
All checks were successful
AI Code Review / ai-review (pull_request) Has been skipped
2026-04-22 11:28:21 +02:00
01ba25f097 email notifications
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 13s
2026-04-21 12:27:43 +02:00
c3b2b03c13 no latest tag 2026-04-21 10:34:17 +02:00
9ad7efc09d Merge branch 'main' into feature/ai-review
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 3s
2026-04-21 08:20:04 +00:00
d7ac8b5b26 pr types
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 4s
2026-04-21 10:19:33 +02:00
c4f6a1c028 doc
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 13s
2026-04-21 09:38:36 +02:00
a3507fd7f1 debug
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 13s
2026-04-21 09:25:45 +02:00
72ab85d0cd token fix
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 24s
2026-04-21 08:52:40 +02:00
077be9fbf3 cmd
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 4s
2026-04-20 13:39:44 +02:00
16da2fa6b3 vars
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 3s
2026-04-20 13:39:02 +02:00
9ab283f1e5 workflow fix
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 9s
2026-04-20 13:37:40 +02:00
e06b270e67 pip
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 8s
2026-04-20 13:11:30 +02:00
89d2952d7a flag
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 8s
2026-04-20 13:09:06 +02:00
3d6eadf128 workflow fix
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 9s
2026-04-20 13:07:50 +02:00
260b45637e AI-review
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 8s
2026-04-20 13:02:48 +02:00
70 changed files with 38 additions and 1188 deletions

View File

@@ -34,7 +34,6 @@ jobs:
with:
submodules: true
fetch-depth: 0
token: ${{ secrets.AI_REVIEW_TOKEN }}
- name: Run inline review
uses: docker://nikitafilonov/ai-review:v0.64.0

View File

@@ -1,32 +0,0 @@
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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/aks-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/aks-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/eks-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/eks-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/gke-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/gke-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -18,7 +18,7 @@ metadata:
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD
path: infra/overlays/upc-prod
destination:

View File

@@ -37,7 +37,7 @@ spec:
- $values/infra/values/base/dot-ai-stack-values.yaml
- $values/infra/values/upc-dev/dot-ai-stack-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD
ref: values

View File

@@ -2,7 +2,7 @@
# in case of $'\r': command not found error, run command below first
# sed -i 's/\r$//' ./bootstrap.sh
CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod|aks-dev|aks-prod|eks-dev|eks-prod|gke-dev|gke-prod)}"
CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod)}"
echo "running $0 for cluster: ${CLUSTER}..."

View File

@@ -57,17 +57,17 @@ spec:
- sh
- -c
- |
mc alias set s3 "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
mc alias set upcloud "${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 "s3/${S3_BUCKET}/${KEY}" && \
mc cp /backup/gitea-dump.zip "upcloud/${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 "s3/${S3_BUCKET}/" 2>&1 || true
mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true
echo "Pruning complete."
envFrom:
- secretRef:

View File

@@ -1,10 +0,0 @@
clusterName: dev-fd-aks
domain: forteapps.net
argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.forteapps.net
keycloakDomain: id.forteapps.net
dotaiDomain: kubemcp.forteapps.net
dotaiUiDomain: kubemcpui.forteapps.net
letsencryptEmail: danijels@gmail.com
trustedIPs: "10.0.0.0/8"
cloudProvider: azure

View File

@@ -1,10 +0,0 @@
clusterName: prod-fd-aks
domain: fortedigital.com
argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.fortedigital.com
keycloakDomain: id.fortedigital.com
dotaiDomain: kubemcp.fortedigital.com
dotaiUiDomain: kubemcpui.fortedigital.com
letsencryptEmail: danijel.simeunovic@fortedigital.com
trustedIPs: "10.0.0.0/8"
cloudProvider: azure

View File

@@ -1,10 +0,0 @@
clusterName: dev-fd-eks
domain: forteapps.net
argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.forteapps.net
keycloakDomain: id.forteapps.net
dotaiDomain: kubemcp.forteapps.net
dotaiUiDomain: kubemcpui.forteapps.net
letsencryptEmail: danijels@gmail.com
trustedIPs: "10.0.0.0/8"
cloudProvider: aws

View File

@@ -1,10 +0,0 @@
clusterName: prod-fd-eks
domain: fortedigital.com
argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.fortedigital.com
keycloakDomain: id.fortedigital.com
dotaiDomain: kubemcp.fortedigital.com
dotaiUiDomain: kubemcpui.fortedigital.com
letsencryptEmail: danijel.simeunovic@fortedigital.com
trustedIPs: "10.0.0.0/8"
cloudProvider: aws

View File

@@ -1,10 +0,0 @@
clusterName: dev-fd-gke
domain: forteapps.net
argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.forteapps.net
keycloakDomain: id.forteapps.net
dotaiDomain: kubemcp.forteapps.net
dotaiUiDomain: kubemcpui.forteapps.net
letsencryptEmail: danijels@gmail.com
trustedIPs: "10.0.0.0/8"
cloudProvider: gcp

View File

@@ -1,10 +0,0 @@
clusterName: prod-fd-gke
domain: fortedigital.com
argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.fortedigital.com
keycloakDomain: id.fortedigital.com
dotaiDomain: kubemcp.fortedigital.com
dotaiUiDomain: kubemcpui.fortedigital.com
letsencryptEmail: danijel.simeunovic@fortedigital.com
trustedIPs: "10.0.0.0/8"
cloudProvider: gcp

View File

@@ -9,7 +9,6 @@
- [Kyverno Policies](#kyverno-policies)
- [Configuration Reference](#configuration-reference)
- [API Endpoints](#api-endpoints)
- [Cloud Overlay Pattern](#cloud-overlay-pattern)
- [Glossary](#glossary)
---
@@ -20,9 +19,9 @@
| Component | Value |
|-----------|-------|
| **Provider** | Multi-cloud (UpCloud, AKS, EKS, GKE) |
| **Environment** | Dev + Production per cloud |
| **Cluster Count** | Multi-cluster (upc-dev/prod, aks-dev/prod, eks-dev/prod, gke-dev/prod) |
| **Provider** | UpCloud Managed Kubernetes |
| **Environment** | Production (internal use) |
| **Cluster Count** | Multi-cluster (upc-dev, upc-prod) |
| **GitOps Tool** | ArgoCD |
| **Ingress Controller** | Traefik v2 |
| **Certificate Management** | Cert-Manager + Let's Encrypt |
@@ -93,34 +92,16 @@ launchpad/
│ ├── sealedsecrets.yaml
│ ├── secrets.yaml
│ ├── renovate.yaml
│ ├── base/ # ArgoCD Application manifests (Kustomize base)
│ │ ├── gitea.yaml
│ │ ├── opencost.yaml
│ │ ├── traefik-application.yaml
│ │ ├── keycloak.yaml
│ │ ├── grafana.yaml
│ │ └── ...
│ ├── overlays/
│ │ └── upc-prod/
│ │ └── kustomization.yaml # Patches upc-dev → upc-prod valueFile paths
│ └── values/
│ ├── base/ # Cloud-agnostic Helm values
│ ├── gitea-values.yaml
│ ├── opencost-values.yaml
│ ├── prometheus-values.yaml
│ └── ...
│ ├── upc-dev/ # UpCloud dev overlay values
│ ├── traefik-values.yaml
│ ├── keycloak-values.yaml
│ ├── grafana-values.yaml
│ │ ├── gitea-values.yaml
│ │ └── opencost-values.yaml
│ └── upc-prod/ # UpCloud prod overlay values
│ ├── traefik-values.yaml
│ ├── keycloak-values.yaml
│ ├── grafana-values.yaml
│ ├── gitea-values.yaml
│ └── opencost-values.yaml
│ ├── argocd-values.yaml
├── prometheus-values.yaml
├── grafana-values.yaml
├── loki-values.yaml
├── tempo-values.yaml
│ ├── gitea-values.yaml
├── gitea-actions-values.yaml
├── fluent-bit-values.yaml
└── renovate-values.yaml
├── apps/ # Business applications
│ ├── mcp10x.yaml
@@ -154,15 +135,6 @@ launchpad/
│ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.yaml
├── scripts/ # Operational helper scripts
│ ├── gitea-backup.sh # S3 backup helper (list/download)
│ ├── gitea-restore.sh
│ └── backup/ # Per-cloud backup reference scripts
│ ├── s3-minio.sh # S3-compatible (UpCloud, MinIO, Wasabi)
│ ├── aws-s3.sh # Native AWS S3
│ ├── azure-blob.sh # Azure Blob Storage
│ └── gcp-gcs.sh # GCP Cloud Storage
├── private/ # Local-only (Git-ignored)
│ ├── *.yaml
│ └── *.sh
@@ -1649,87 +1621,6 @@ POST /loki/api/v1/push
---
## Cloud Overlay Pattern
### Overview
Cloud-specific configuration (StorageClass, LoadBalancer annotations, pricing models, etc.) lives in per-cloud overlay value files, **not** in `base/`. Adding a new cloud provider only requires a new overlay directory — no base changes.
### Supported Clouds
| Cloud | Dev overlay | Prod overlay | StorageClass | LB type |
|-------|-----------|-------------|-------------|---------|
| **UpCloud** | `upc-dev` | `upc-prod` | `upcloud-block-storage-maxiops` | UpCloud LB (proxy protocol v2) |
| **Azure AKS** | `aks-dev` | `aks-prod` | `managed-csi-premium` | Azure LB |
| **AWS EKS** | `eks-dev` | `eks-prod` | `gp3` | AWS NLB (proxy protocol) |
| **GCP GKE** | `gke-dev` | `gke-prod` | `premium-rwo` | GCP NEG |
Bootstrap any cluster with: `./bootstrap.sh <cluster>` (e.g., `./bootstrap.sh aks-dev`)
### How It Works
Each ArgoCD Application uses **multi-source Helm values** with two value files:
```yaml
# infra/base/gitea.yaml (example)
helm:
valueFiles:
- $values/infra/values/base/gitea-values.yaml # [0] cloud-agnostic
- $values/infra/values/upc-dev/gitea-values.yaml # [1] cloud-specific (default: upc-dev)
```
The `upc-prod` Kustomize overlay patches index `[1]` to swap the cloud-specific file:
```yaml
# infra/overlays/upc-prod/kustomization.yaml
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/gitea-values.yaml
```
### Components Using Cloud Overlays
| Component | Cloud-specific config | Overlay value file |
|-----------|----------------------|-------------------|
| **Traefik** | LB annotations, proxy protocol IPs | `traefik-values.yaml` |
| **Keycloak** | Hostname, TLS settings | `keycloak-values.yaml` |
| **Grafana** | Hostname, datasource URLs | `grafana-values.yaml` |
| **Gitea** | StorageClass (persistence + PostgreSQL) | `gitea-values.yaml` |
| **OpenCost** | Custom pricing model (CPU/RAM/storage rates) | `opencost-values.yaml` |
### Backup CronJob
The `gitea-backup` CronJob uses a generic `s3` alias for `minio/mc`. The actual endpoint and credentials come from the `gitea-backup-s3` Sealed Secret, which is per-cloud. Reference scripts for different cloud providers are in `scripts/backup/`:
| Script | Provider | Tool |
|--------|----------|------|
| `s3-minio.sh` | S3-compatible (UpCloud, MinIO, Wasabi) | `minio/mc` |
| `aws-s3.sh` | AWS S3 | `aws` CLI |
| `azure-blob.sh` | Azure Blob Storage | `az` CLI |
| `gcp-gcs.sh` | GCP Cloud Storage | `gsutil` |
### Adding a New Cloud Provider
To add support for a new cloud (e.g., `oci-dev` for Oracle Cloud):
1. **Cluster config**: `clusters/oci-dev.yaml` — clusterName, domain, trustedIPs, cloudProvider
2. **Overlay value files** in `infra/values/oci-dev/`:
- `traefik-values.yaml` — LB annotations, proxy protocol config
- `keycloak-values.yaml` — hostname
- `grafana-values.yaml` — hostname
- `gitea-values.yaml``storageClass` for persistence + PostgreSQL
- `opencost-values.yaml` — pricing model or cloud billing integration
3. **Kustomize overlay**: `infra/overlays/oci-dev/kustomization.yaml` — patch `valueFiles[1]` for each Application
4. **App-of-apps**: `_app-of-apps-oci-dev.yaml` — points to `infra/overlays/oci-dev`
5. **Sealed Secrets**: `secrets/oci-dev/` — TLS certs, credentials, backup S3 config
6. **Bootstrap**: `./bootstrap.sh oci-dev`
---
## Glossary
### Terms

View File

@@ -22,7 +22,6 @@ 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

View File

@@ -22,9 +22,8 @@ spec:
releaseName: opencost
valueFiles:
- $values/infra/values/base/opencost-values.yaml
- $values/infra/values/upc-dev/opencost-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD
ref: values

View File

@@ -31,7 +31,7 @@ spec:
- $values/infra/values/base/traefik-values.yaml
- $values/infra/values/upc-dev/traefik-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD
ref: values

View File

@@ -1,68 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# Traefik: swap upc-dev → aks-dev
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-dev/traefik-values.yaml
# Keycloak: swap upc-dev → aks-dev
- target:
kind: Application
name: keycloak
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-dev/keycloak-values.yaml
# Grafana: swap upc-dev → aks-dev
- target:
kind: Application
name: grafana
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-dev/grafana-values.yaml
# Gitea: swap upc-dev → aks-dev
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-dev/gitea-values.yaml
# OpenCost: swap upc-dev → aks-dev
- target:
kind: Application
name: opencost
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-dev/opencost-values.yaml
# Secrets: change path to aks-dev
- target:
kind: Application
name: secrets
patch: |
- op: replace
path: /spec/source/path
value: secrets/aks-dev
# Enterprise-apps: point to aks-dev overlay
- target:
kind: Application
name: enterprise-apps
patch: |
- op: replace
path: /spec/source/path
value: apps/overlays/aks-dev

View File

@@ -1,68 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# Traefik: swap upc-dev → aks-prod
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-prod/traefik-values.yaml
# Keycloak: swap upc-dev → aks-prod
- target:
kind: Application
name: keycloak
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-prod/keycloak-values.yaml
# Grafana: swap upc-dev → aks-prod
- target:
kind: Application
name: grafana
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-prod/grafana-values.yaml
# Gitea: swap upc-dev → aks-prod
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-prod/gitea-values.yaml
# OpenCost: swap upc-dev → aks-prod
- target:
kind: Application
name: opencost
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/aks-prod/opencost-values.yaml
# Secrets: change path to aks-prod
- target:
kind: Application
name: secrets
patch: |
- op: replace
path: /spec/source/path
value: secrets/aks-prod
# Enterprise-apps: point to aks-prod overlay
- target:
kind: Application
name: enterprise-apps
patch: |
- op: replace
path: /spec/source/path
value: apps/overlays/aks-prod

View File

@@ -1,68 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# Traefik: swap upc-dev → eks-dev
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-dev/traefik-values.yaml
# Keycloak: swap upc-dev → eks-dev
- target:
kind: Application
name: keycloak
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-dev/keycloak-values.yaml
# Grafana: swap upc-dev → eks-dev
- target:
kind: Application
name: grafana
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-dev/grafana-values.yaml
# Gitea: swap upc-dev → eks-dev
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-dev/gitea-values.yaml
# OpenCost: swap upc-dev → eks-dev
- target:
kind: Application
name: opencost
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-dev/opencost-values.yaml
# Secrets: change path to eks-dev
- target:
kind: Application
name: secrets
patch: |
- op: replace
path: /spec/source/path
value: secrets/eks-dev
# Enterprise-apps: point to eks-dev overlay
- target:
kind: Application
name: enterprise-apps
patch: |
- op: replace
path: /spec/source/path
value: apps/overlays/eks-dev

View File

@@ -1,68 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# Traefik: swap upc-dev → eks-prod
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-prod/traefik-values.yaml
# Keycloak: swap upc-dev → eks-prod
- target:
kind: Application
name: keycloak
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-prod/keycloak-values.yaml
# Grafana: swap upc-dev → eks-prod
- target:
kind: Application
name: grafana
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-prod/grafana-values.yaml
# Gitea: swap upc-dev → eks-prod
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-prod/gitea-values.yaml
# OpenCost: swap upc-dev → eks-prod
- target:
kind: Application
name: opencost
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/eks-prod/opencost-values.yaml
# Secrets: change path to eks-prod
- target:
kind: Application
name: secrets
patch: |
- op: replace
path: /spec/source/path
value: secrets/eks-prod
# Enterprise-apps: point to eks-prod overlay
- target:
kind: Application
name: enterprise-apps
patch: |
- op: replace
path: /spec/source/path
value: apps/overlays/eks-prod

View File

@@ -1,68 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# Traefik: swap upc-dev → gke-dev
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-dev/traefik-values.yaml
# Keycloak: swap upc-dev → gke-dev
- target:
kind: Application
name: keycloak
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-dev/keycloak-values.yaml
# Grafana: swap upc-dev → gke-dev
- target:
kind: Application
name: grafana
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-dev/grafana-values.yaml
# Gitea: swap upc-dev → gke-dev
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-dev/gitea-values.yaml
# OpenCost: swap upc-dev → gke-dev
- target:
kind: Application
name: opencost
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-dev/opencost-values.yaml
# Secrets: change path to gke-dev
- target:
kind: Application
name: secrets
patch: |
- op: replace
path: /spec/source/path
value: secrets/gke-dev
# Enterprise-apps: point to gke-dev overlay
- target:
kind: Application
name: enterprise-apps
patch: |
- op: replace
path: /spec/source/path
value: apps/overlays/gke-dev

View File

@@ -1,68 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# Traefik: swap upc-dev → gke-prod
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-prod/traefik-values.yaml
# Keycloak: swap upc-dev → gke-prod
- target:
kind: Application
name: keycloak
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-prod/keycloak-values.yaml
# Grafana: swap upc-dev → gke-prod
- target:
kind: Application
name: grafana
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-prod/grafana-values.yaml
# Gitea: swap upc-dev → gke-prod
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-prod/gitea-values.yaml
# OpenCost: swap upc-dev → gke-prod
- target:
kind: Application
name: opencost
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/gke-prod/opencost-values.yaml
# Secrets: change path to gke-prod
- target:
kind: Application
name: secrets
patch: |
- op: replace
path: /spec/source/path
value: secrets/gke-prod
# Enterprise-apps: point to gke-prod overlay
- target:
kind: Application
name: enterprise-apps
patch: |
- op: replace
path: /spec/source/path
value: apps/overlays/gke-prod

View File

@@ -31,24 +31,6 @@ patches:
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/grafana-values.yaml
# 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
# Secrets: change path to upc-prod
- target:
kind: Application

View File

@@ -1,7 +0,0 @@
# AKS-specific: Azure managed disk storage class
persistence:
storageClass: managed-csi-premium
postgresql:
primary:
persistence:
storageClass: managed-csi-premium

View File

@@ -1,4 +0,0 @@
# AKS-specific: Grafana hostname
ingress:
hosts:
- grafana.forteapps.net

View File

@@ -1,3 +0,0 @@
# AKS-specific: Keycloak hostname
ingress:
hostname: id.forteapps.net

View File

@@ -1,8 +0,0 @@
# AKS-specific: Azure pricing via Cloud Billing API
opencost:
exporter:
cloudProviderApiKey: ""
customPricing:
enabled: false
azure:
secretName: opencost-azure-billing

View File

@@ -1,11 +0,0 @@
# AKS-specific: Azure Load Balancer for Traefik
service:
annotations:
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: /ping
ports:
web:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"
websecure:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"

View File

@@ -1,7 +0,0 @@
# AKS-specific: Azure managed disk storage class (prod)
persistence:
storageClass: managed-csi-premium
postgresql:
primary:
persistence:
storageClass: managed-csi-premium

View File

@@ -1,4 +0,0 @@
# AKS-specific: Grafana hostname (prod)
ingress:
hosts:
- grafana.fortedigital.com

View File

@@ -1,3 +0,0 @@
# AKS-specific: Keycloak hostname (prod)
ingress:
hostname: id.fortedigital.com

View File

@@ -1,8 +0,0 @@
# AKS-specific: Azure pricing via Cloud Billing API (prod)
opencost:
exporter:
cloudProviderApiKey: ""
customPricing:
enabled: false
azure:
secretName: opencost-azure-billing

View File

@@ -1,12 +0,0 @@
# AKS-specific: Azure Load Balancer for Traefik (prod)
service:
annotations:
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: /ping
service.beta.kubernetes.io/azure-load-balancer-internal: "false"
ports:
web:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"
websecure:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"

View File

@@ -130,6 +130,7 @@ persistence:
size: 10Gi
accessModes:
- ReadWriteOnce
storageClass: upcloud-block-storage-maxiops
# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes
strategy:
@@ -155,6 +156,7 @@ postgresql:
persistence:
enabled: true
size: 8Gi
storageClass: upcloud-block-storage-maxiops
resources:
requests:
cpu: 100m

View File

@@ -10,6 +10,18 @@ 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"
ui:
enabled: false
service:

View File

@@ -1,7 +0,0 @@
# EKS-specific: gp3 storage class
persistence:
storageClass: gp3
postgresql:
primary:
persistence:
storageClass: gp3

View File

@@ -1,4 +0,0 @@
# EKS-specific: Grafana hostname
ingress:
hosts:
- grafana.forteapps.net

View File

@@ -1,3 +0,0 @@
# EKS-specific: Keycloak hostname
ingress:
hostname: id.forteapps.net

View File

@@ -1,11 +0,0 @@
# EKS-specific: AWS pricing via Cost and Usage Report
opencost:
exporter:
cloudProviderApiKey: ""
customPricing:
enabled: false
aws:
spot_data_region: ""
spot_data_bucket: ""
spot_data_prefix: ""
account_id: ""

View File

@@ -1,17 +0,0 @@
# EKS-specific: AWS NLB for Traefik
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
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"
forwardedHeaders:
trustedIPs: "10.0.0.0/8"
websecure:
proxyProtocol:
trustedIPs: "10.0.0.0/8"
forwardedHeaders:
trustedIPs: "10.0.0.0/8"

View File

@@ -1,7 +0,0 @@
# EKS-specific: gp3 storage class (prod)
persistence:
storageClass: gp3
postgresql:
primary:
persistence:
storageClass: gp3

View File

@@ -1,4 +0,0 @@
# EKS-specific: Grafana hostname (prod)
ingress:
hosts:
- grafana.fortedigital.com

View File

@@ -1,3 +0,0 @@
# EKS-specific: Keycloak hostname (prod)
ingress:
hostname: id.fortedigital.com

View File

@@ -1,11 +0,0 @@
# EKS-specific: AWS pricing via Cost and Usage Report (prod)
opencost:
exporter:
cloudProviderApiKey: ""
customPricing:
enabled: false
aws:
spot_data_region: ""
spot_data_bucket: ""
spot_data_prefix: ""
account_id: ""

View File

@@ -1,18 +0,0 @@
# EKS-specific: AWS NLB for Traefik (prod)
service:
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
ports:
web:
proxyProtocol:
trustedIPs: "10.0.0.0/8"
forwardedHeaders:
trustedIPs: "10.0.0.0/8"
websecure:
proxyProtocol:
trustedIPs: "10.0.0.0/8"
forwardedHeaders:
trustedIPs: "10.0.0.0/8"

View File

@@ -1,7 +0,0 @@
# GKE-specific: SSD persistent disk storage class
persistence:
storageClass: premium-rwo
postgresql:
primary:
persistence:
storageClass: premium-rwo

View File

@@ -1,4 +0,0 @@
# GKE-specific: Grafana hostname
ingress:
hosts:
- grafana.forteapps.net

View File

@@ -1,3 +0,0 @@
# GKE-specific: Keycloak hostname
ingress:
hostname: id.forteapps.net

View File

@@ -1,10 +0,0 @@
# GKE-specific: GCP pricing via BigQuery billing export
opencost:
exporter:
cloudProviderApiKey: ""
customPricing:
enabled: false
google:
key: ""
project_id: ""
billing_account: ""

View File

@@ -1,12 +0,0 @@
# GKE-specific: Google Cloud Load Balancer for Traefik
service:
annotations:
cloud.google.com/neg: '{"ingress":true}'
networking.gke.io/load-balancer-type: External
ports:
web:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"
websecure:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"

View File

@@ -1,7 +0,0 @@
# GKE-specific: SSD persistent disk storage class (prod)
persistence:
storageClass: premium-rwo
postgresql:
primary:
persistence:
storageClass: premium-rwo

View File

@@ -1,4 +0,0 @@
# GKE-specific: Grafana hostname (prod)
ingress:
hosts:
- grafana.fortedigital.com

View File

@@ -1,3 +0,0 @@
# GKE-specific: Keycloak hostname (prod)
ingress:
hostname: id.fortedigital.com

View File

@@ -1,10 +0,0 @@
# GKE-specific: GCP pricing via BigQuery billing export (prod)
opencost:
exporter:
cloudProviderApiKey: ""
customPricing:
enabled: false
google:
key: ""
project_id: ""
billing_account: ""

View File

@@ -1,12 +0,0 @@
# GKE-specific: Google Cloud Load Balancer for Traefik (prod)
service:
annotations:
cloud.google.com/neg: '{"ingress":true}'
networking.gke.io/load-balancer-type: External
ports:
web:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"
websecure:
forwardedHeaders:
trustedIPs: "10.0.0.0/8"

View File

@@ -1,7 +0,0 @@
# UpCloud-specific: block storage class for Gitea + PostgreSQL
persistence:
storageClass: upcloud-block-storage-maxiops
postgresql:
primary:
persistence:
storageClass: upcloud-block-storage-maxiops

View File

@@ -1,15 +0,0 @@
# UpCloud-specific: custom pricing model
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"

View File

@@ -1,7 +0,0 @@
# UpCloud-specific: block storage class for Gitea + PostgreSQL
persistence:
storageClass: upcloud-block-storage-maxiops
postgresql:
primary:
persistence:
storageClass: upcloud-block-storage-maxiops

View File

@@ -1,15 +0,0 @@
# UpCloud-specific: custom pricing model
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"

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# AWS S3 backup upload (native AWS CLI)
# Uses: aws cli v2
# Env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, S3_BUCKET
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip"
echo "Uploading ${KEY}..."
aws s3 cp "$BACKUP_FILE" "s3://${S3_BUCKET}/${KEY}"
echo "Upload complete."
# Prune backups older than 7 days
echo "Pruning backups older than 7 days..."
CUTOFF=$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%S 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%S)
aws s3api list-objects-v2 --bucket "${S3_BUCKET}" --query "Contents[?LastModified<'${CUTOFF}'].Key" --output text \
| tr '\t' '\n' \
| while read -r key; do
[ -n "$key" ] && aws s3 rm "s3://${S3_BUCKET}/${key}" && echo "Deleted: ${key}"
done
echo "Pruning complete."

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Azure Blob Storage backup upload
# Uses: az cli
# Env: AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_KEY, AZURE_CONTAINER
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip"
echo "Uploading ${KEY}..."
az storage blob upload \
--account-name "${AZURE_STORAGE_ACCOUNT}" \
--account-key "${AZURE_STORAGE_KEY}" \
--container-name "${AZURE_CONTAINER}" \
--name "${KEY}" \
--file "$BACKUP_FILE" \
--overwrite
echo "Upload complete."
# Prune backups older than 7 days
echo "Pruning backups older than 7 days..."
CUTOFF=$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%SZ)
az storage blob list \
--account-name "${AZURE_STORAGE_ACCOUNT}" \
--account-key "${AZURE_STORAGE_KEY}" \
--container-name "${AZURE_CONTAINER}" \
--query "[?properties.lastModified<'${CUTOFF}'].name" -o tsv \
| while read -r name; do
[ -n "$name" ] && az storage blob delete \
--account-name "${AZURE_STORAGE_ACCOUNT}" \
--account-key "${AZURE_STORAGE_KEY}" \
--container-name "${AZURE_CONTAINER}" \
--name "$name" && echo "Deleted: ${name}"
done
echo "Pruning complete."

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# GCP Cloud Storage backup upload
# Uses: gsutil (gcloud SDK)
# Env: GCS_BUCKET (e.g. gs://my-bucket)
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip"
echo "Uploading ${KEY}..."
gsutil cp "$BACKUP_FILE" "${GCS_BUCKET}/${KEY}"
echo "Upload complete."
# Prune backups older than 7 days — GCS lifecycle rules are preferred,
# but this works as a manual fallback
echo "Pruning backups older than 7 days..."
CUTOFF=$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%SZ)
gsutil ls -l "${GCS_BUCKET}/" \
| grep 'gitea-dump-' \
| while read -r size date name; do
if [[ "$date" < "$CUTOFF" ]]; then
gsutil rm "$name" && echo "Deleted: ${name}"
fi
done
echo "Pruning complete."

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# S3-compatible backup upload (UpCloud Objects, MinIO, Wasabi, etc.)
# Uses: minio/mc
# Env: S3_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET
BACKUP_FILE="${1:?Usage: $0 <backup-file>}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip"
mc alias set s3 "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
echo "Uploading ${KEY}..."
mc cp "$BACKUP_FILE" "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 "s3/${S3_BUCKET}/" 2>&1 || true
echo "Pruning complete."

View File

@@ -13,7 +13,7 @@ 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'
ALIAS_CMD='mc alias set upcloud ${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
@@ -41,7 +41,7 @@ mc_run() {
case "${1:-help}" in
list)
echo "Listing backups..."
mc_run 'mc ls s3/${S3_BUCKET}/'
mc_run 'mc ls upcloud/${S3_BUCKET}/'
;;
download)
@@ -49,7 +49,7 @@ case "${1:-help}" in
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:]')
FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
if [ -z "$FILE" ]; then
echo "No backups found."
exit 1
@@ -74,7 +74,7 @@ case "${1:-help}" in
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"
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE"
cleanup
echo "Downloaded: ./$FILE"

Submodule shared-prompts deleted from c5bc55b3d7