41 Commits

Author SHA1 Message Date
9a7e03b794 Merge branch 'feature/cloud-agnostic' into feature/multi-cloud 2026-04-22 22:06:31 +02:00
f1dd61cece sync 2026-04-22 21:56:43 +02:00
acc9bb1a85 sync 2026-04-22 21:53:44 +02:00
c8c2dedea5 rename 2026-04-22 21:48:02 +02:00
a471f11740 repo url 2026-04-22 14:45:23 +02:00
92ddc22322 azure>aks 2026-04-22 14:42:02 +02:00
7d2fb8bc0c azure>aks 2026-04-22 14:41:42 +02:00
79f9c62012 azure>aks 2026-04-22 14:35:59 +02:00
dea54e469e repo url 2026-04-22 14:34:20 +02:00
333acdea26 multi-cloud overlays
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 6s
2026-04-22 14:30:13 +02:00
03d526208b Merge branch 'main' into feature/cloud-agnostic
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 7s
2026-04-22 12:08:08 +00:00
458f7b23ad Merge branch 'main' into feature/multi-cloud
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 28s
2026-04-22 11:55:05 +00:00
5df104beec sp 2026-04-22 13:54:51 +02:00
41c8b85bf8 Merge branch 'main' into feature/multi-cloud
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 26s
2026-04-22 11:52:22 +00:00
0ecfee3cf8 prompts 2026-04-22 13:51:38 +02:00
c3f723333b Merge branch 'feature/cloud-agnostic' of ssh://git.forteapps.net:2222/Forte/launchpad into feature/cloud-agnostic
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 1m3s
2026-04-22 13:43:09 +02:00
4144b1c1ac token 2026-04-22 13:39:43 +02:00
16eadbe181 Merge remote-tracking branch 'origin/main' into feature/cloud-agnostic 2026-04-22 13:38:55 +02:00
4e6a84785a token
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 28s
2026-04-22 13:37:32 +02:00
e0bdaab422 multi-cloud + mcp
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 2s
2026-04-22 13:34:48 +02:00
230ea7ebeb Merge branch 'main' into feature/cloud-agnostic
Some checks failed
AI Code Review / ai-review (pull_request) Failing after 3s
2026-04-22 11:33:03 +00:00
cab0866e14 multi-cloud no mcp 2026-04-22 13:31:09 +02:00
c88938adb5 feature/ai-review (#7)
Co-authored-by: gitea_admin <admin@forteapps.net>
Reviewed-on: #7
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-22 09:30:02 +00:00
d05a16840e pr trigger 2026-04-22 09:11:40 +02:00
d7c7242aa1 submodule 2026-04-22 09:10:38 +02:00
3bf9fa7837 pr label 2026-04-22 08:48:05 +02:00
d2596568f2 version tag 2026-04-21 15:17:52 +02:00
2a3539350b AI-review (#6)
Co-authored-by: gitea_admin <admin@forteapps.net>
Reviewed-on: #6
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-21 08:20:41 +00:00
f97b613c12 remove unneeded yml 2026-04-20 22:46:44 +02:00
9c7db11470 remove unneeded yml 2026-04-20 22:45:53 +02:00
723072bd1e cleanup 2026-04-19 13:47:29 +02:00
046b78446b add opencost 2026-04-19 13:41:44 +02:00
56a1b49d10 missing manifest 2026-04-19 13:39:26 +02:00
d557eb1865 revert 2026-04-19 13:28:40 +02:00
a51ed84124 Merge branch 'main' of https://git.forteapps.net/Forte/launchpad 2026-04-19 13:28:03 +02:00
73e253a579 traefik 2026-04-19 13:27:59 +02:00
d7c1341eab don't sync users with cron job 2026-04-19 11:43:47 +02:00
eed53006c1 docs 2026-04-18 23:12:18 +02:00
395ca70c2a prod values 2026-04-18 23:02:02 +02:00
ea04ec20c9 remove docs wf 2026-04-18 20:54:48 +02:00
03a0d7c9ae feature/multicluster
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 5s
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@trumf.no>
Reviewed-on: #4
Reviewed-by: gitea_admin <admin@forteapps.net>
2026-04-18 18:14:00 +00:00
91 changed files with 1898 additions and 296 deletions

View File

@@ -0,0 +1,47 @@
name: AI Code Review
on:
pull_request:
types: [ labeled, synchronize ]
jobs:
ai-review:
if: >-
(github.event.action == 'synchronized' && contains(toJSON(github.event.pull_request.labels), 'ai-review')) || contains(toJSON(gitea.event.changes.added_labels), 'ai-review')
runs-on: ubuntu-latest
env:
AI_REVIEW_CONFIG_FILE_YAML: ./shared-prompts/iac/.ai-review.yaml
# VCS configuration
VCS__PROVIDER: GITEA
VCS__PIPELINE__OWNER: ${{ github.repository_owner }}
VCS__PIPELINE__REPO: ${{ github.event.repository.name }}
VCS__PIPELINE__PULL_NUMBER: ${{ github.event.pull_request.number }}
VCS__HTTP_CLIENT__API_URL: https://git.forteapps.net/api/v1
VCS__HTTP_CLIENT__API_TOKEN: ${{ secrets.AI_REVIEW_TOKEN }}
# Review — disable fallback to see real Gitea API errors
REVIEW__INLINE_COMMENT_FALLBACK: "false"
# LLM configuration
LLM__PROVIDER: CLAUDE
LLM__META__MODEL: claude-sonnet-4-20250514
LLM__META__MAX_TOKENS: "4096"
LLM__HTTP_CLIENT__API_URL: https://api.anthropic.com
LLM__HTTP_CLIENT__API_TOKEN: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
token: ${{ secrets.AI_REVIEW_TOKEN }}
- name: Run inline review
uses: docker://nikitafilonov/ai-review:v0.64.0
with:
args: ai-review run-inline
- name: Run summary review
uses: docker://nikitafilonov/ai-review:v0.64.0
with:
args: ai-review run-summary

View File

@@ -1,34 +0,0 @@
name: Deploy Gitea Pages
on:
push:
branches: [ main ]
paths:
- 'docs/**'
- 'mkdocs.yml'
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update -qq
apt-get install -y -qq python3-pip
pip3 install --break-system-packages mkdocs mkdocs-material
- run: mkdocs build
- name: Deploy to Gitea Pages
run: |
cd site
git init
git config user.name "gitea-actions"
git config user.email "actions@forteapps.net"
git add .
git commit -m "Deploy docs"
git push --force "https://x-token:${{ secrets.GITEA_TOKEN }}@git.forteapps.net/Forte/launchpad.git" HEAD:gitea-pages

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "shared-prompts"]
path = shared-prompts
url = https://git.forteapps.net/Forte/ai-review-prompts.git

View File

@@ -1,7 +0,0 @@
standards_version: "2025.1"
last_configured: "2026-04-04"
components:
github-pages: "2025.1"
github-pages-generator: "mkdocs"
github-pages-source: "docs/"
github-pages-theme: "material"

View File

@@ -1,9 +1,9 @@
# Kubernetes Cluster - GitOps Configuration # 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/) [![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 │ │ ├── renovate.yaml
│ │ ├── ... # All other Application manifests │ │ ├── ... # All other Application manifests
│ │ └── secrets.yaml │ │ └── secrets.yaml
│ ├── overlays/ # Per-cluster overrides │ ├── overlays/ # Per-cluster overrides (Kustomize)
│ │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is) │ │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
│ │ ── upc-prod/ # UpCloud Prod cluster (patches value paths) │ │ ── upc-prod/ # UpCloud Prod (patches value paths)
│ │ ├── eks-dev/ # AWS EKS Dev
│ │ ├── eks-prod/ # AWS EKS Prod
│ │ ├── aks-dev/ # Azure AKS Dev
│ │ ├── aks-prod/ # Azure AKS Prod
│ │ ├── gke-dev/ # GCP GKE Dev
│ │ └── gke-prod/ # GCP GKE Prod
│ ├── dashboards/ # Grafana dashboard ConfigMaps │ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides │ └── values/ # Helm value overrides
│ ├── base/ # Shared values (all clusters) │ ├── base/ # Shared cloud-agnostic values
│ ├── upc-dev/ # UpCloud Dev-specific values │ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing)
── upc-prod/ # UpCloud Prod-specific values ── upc-prod/ # UpCloud Prod
│ ├── eks-dev/ # AWS EKS Dev
│ ├── eks-prod/ # AWS EKS Prod
│ ├── aks-dev/ # Azure AKS Dev
│ ├── aks-prod/ # Azure AKS Prod
│ ├── gke-dev/ # GCP GKE Dev
│ └── gke-prod/ # GCP GKE Prod
├── apps/ # Business Applications ├── apps/ # Business Applications
│ ├── mcp10x.yaml │ ├── mcp10x.yaml
@@ -146,12 +158,12 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
|------------|---------|-----------|-----------| |------------|---------|-----------|-----------|
| **[launchpad](https://git.forteapps.net/Forte/launchpad)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often | | **[launchpad](https://git.forteapps.net/Forte/launchpad)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often |
| **[forte-helm](https://git.forteapps.net/Forte/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | | **[forte-helm](https://git.forteapps.net/Forte/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely |
| **[helm-values](ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes | | **[helm-prod-values](ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes |
### GitOps Workflow ### GitOps Workflow
``` ```
Developer commits code → CI/CD builds image → Updates helm-values → ArgoCD syncs → Deployed to cluster Developer commits code → CI/CD builds image → Updates helm-prod-values → ArgoCD syncs → Deployed to cluster
``` ```
**Learn more**: [GitOps Architecture - GitOps Workflow](docs/GITOPS-ARCHITECTURE.md#gitops-workflow) **Learn more**: [GitOps Architecture - GitOps Workflow](docs/GITOPS-ARCHITECTURE.md#gitops-workflow)
@@ -166,7 +178,7 @@ Developer commits code → CI/CD builds image → Updates helm-values → ArgoCD
**Quick version**: **Quick version**:
1. Create `apps/myapp.yaml` (ArgoCD Application manifest) 1. Create `apps/myapp.yaml` (ArgoCD Application manifest)
2. Create `helm-values/myapp/values.yaml` (configuration) 2. Create `helm-prod-values/myapp/values.yaml` (configuration)
3. Create sealed secrets if needed 3. Create sealed secrets if needed
4. Commit and push - ArgoCD auto-syncs! 4. Commit and push - ArgoCD auto-syncs!
@@ -175,8 +187,8 @@ Developer commits code → CI/CD builds image → Updates helm-values → ArgoCD
**See detailed guide**: [Developer Guide - Updating an Existing Application](docs/DEVELOPER-GUIDE.md#updating-an-existing-application) **See detailed guide**: [Developer Guide - Updating an Existing Application](docs/DEVELOPER-GUIDE.md#updating-an-existing-application)
**Quick version**: **Quick version**:
- **Update code**: Push to app repo → CI/CD updates image tag in helm-values - **Update code**: Push to app repo → CI/CD updates image tag in helm-prod-values
- **Update config**: Edit `helm-values/myapp/values.yaml` → commit → push - **Update config**: Edit `helm-prod-values/myapp/values.yaml` → commit → push
### Manage Secrets ### Manage Secrets
@@ -204,7 +216,7 @@ git push
**Quick version**: **Quick version**:
```yaml ```yaml
# In helm-values/myapp/values.yaml # In helm-prod-values/myapp/values.yaml
# Token-based auth (simple) # Token-based auth (simple)
auth: auth:
@@ -361,12 +373,12 @@ kubectl patch application myapp -n argocd \
## 📖 Key Concepts ## 📖 Key Concepts
### App-of-Apps Pattern ### 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`, `eks-dev`, `eks-prod`, `aks-dev`, `aks-prod`, `gke-dev`, `gke-prod`.
### Multi-Source Pattern ### Multi-Source Pattern
Applications reference both: Applications reference both:
1. **Helm charts** from `forte-helm` (templates) 1. **Helm charts** from `forte-helm` (templates)
2. **Values** from `helm-values` (configuration) 2. **Values** from `helm-prod-values` (configuration)
This separates reusable templates from environment-specific config. This separates reusable templates from environment-specific config.
@@ -435,7 +447,7 @@ Applications deploy in order using `argocd.argoproj.io/sync-wave`:
### Adding a New Application ### Adding a New Application
1. Read [Developer Guide - Deploying Your First Application](docs/DEVELOPER-GUIDE.md#deploying-your-first-application) 1. Read [Developer Guide - Deploying Your First Application](docs/DEVELOPER-GUIDE.md#deploying-your-first-application)
2. Create ArgoCD Application manifest in `apps/` 2. Create ArgoCD Application manifest in `apps/`
3. Create Helm values in `helm-values/` 3. Create Helm values in `helm-prod-values/`
4. Create sealed secrets if needed 4. Create sealed secrets if needed
5. Commit and push - ArgoCD handles the rest! 5. Commit and push - ArgoCD handles the rest!
@@ -458,16 +470,14 @@ Documentation lives in `docs/`. To update:
## 📝 Notes ## 📝 Notes
### Current Environment ### 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) - **Environment**: Production (internal use only)
- **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
- **Auth**: Disabled for ArgoCD (internal access) - **Auth**: Disabled for ArgoCD (internal access)
- **Backup**: None (cluster rebuildable via GitOps) - **Backup**: Gitea daily backup to S3-compatible storage
### Known Limitations ### Known Limitations
- No automated backups (yet)
- Secret rotation not automated - Secret rotation not automated
- Multi-cluster limited to upc-dev and upc-prod environments
- DNS management is manual - DNS management is manual
**Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery) **Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery)
@@ -485,8 +495,8 @@ Documentation lives in `docs/`. To update:
- [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) - [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
### Related Repositories ### Related Repositories
- [forte-helm](https://github.com/fortedigital/forte-helm) - Helm chart templates - [forte-helm](https://git.forteapps.net/Forte/forte-helm) - Helm chart templates
- [helm-values](git@github.com:fortedigital/helm-values.git) - Application values - [helm-prod-values](git@github.com:fortedigital/helm-prod-values.git) - Application values
--- ---
@@ -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 **Documentation Version**: 1.0.0
**🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!** **🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!**

32
_app-of-apps-aks-dev.yaml Normal file
View File

@@ -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: 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

@@ -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: 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

32
_app-of-apps-eks-dev.yaml Normal file
View File

@@ -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: 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

@@ -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: 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

32
_app-of-apps-gke-dev.yaml Normal file
View File

@@ -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: 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

@@ -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: 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: spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: infra/overlays/upc-prod path: infra/overlays/upc-prod
destination: destination:

View File

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

View File

@@ -4,5 +4,4 @@ resources:
- dot-ai-stack.yaml - dot-ai-stack.yaml
- mcp10x.yaml - mcp10x.yaml
- musicman.yaml - musicman.yaml
- mcpcoder.yaml
- argo-mcp.yaml - argo-mcp.yaml

View File

@@ -2,7 +2,7 @@
# in case of $'\r': command not found error, run command below first # in case of $'\r': command not found error, run command below first
# sed -i 's/\r$//' ./bootstrap.sh # sed -i 's/\r$//' ./bootstrap.sh
CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (eu|us)}" CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod|aks-dev|aks-prod|eks-dev|eks-prod|gke-dev|gke-prod)}"
echo "running $0 for cluster: ${CLUSTER}..." echo "running $0 for cluster: ${CLUSTER}..."
@@ -17,18 +17,18 @@ echo "Bootstrapping cluster: ${clusterName} (${CLUSTER})..."
Bootstrap() Bootstrap()
{ {
ArgoCd ArgoCd
# Github # Gitea
} }
############################################################ ############################################################
# Github # # Gitea #
############################################################ ############################################################
Github() Gitea()
{ {
echo "Installing secret..." echo "Installing secret..."
kubectl apply -f private/github-${CLUSTER}.yaml kubectl apply -f private/gitea-repo-main.yaml
kubectl apply -f private/main-${CLUSTER}.key kubectl apply -f private/main.key
} }
############################################################ ############################################################

View File

@@ -57,17 +57,17 @@ spec:
- sh - sh
- -c - -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) TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip" KEY="gitea-dump-${TIMESTAMP}.zip"
echo "Uploading ${KEY}..." 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." echo "Upload complete."
# Prune backups older than 7 days # Prune backups older than 7 days
echo "Pruning 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." echo "Pruning complete."
envFrom: envFrom:
- secretRef: - secretRef:

10
clusters/aks-dev.yaml Normal file
View File

@@ -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

10
clusters/aks-prod.yaml Normal file
View File

@@ -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

10
clusters/eks-dev.yaml Normal file
View File

@@ -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: eks

10
clusters/eks-prod.yaml Normal file
View File

@@ -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: eks

10
clusters/gke-dev.yaml Normal file
View File

@@ -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: gke

10
clusters/gke-prod.yaml Normal file
View File

@@ -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: gke

View File

@@ -1,10 +1,10 @@
clusterName: dev-fd-us-east1 clusterName: prod-fd-no-svg1
domain: us.forteapps.net domain: fortedigital.com
argocdDomain: argocd.us.forteapps.net argocdDomain: argocd.127.0.0.1.nip.io
grafanaDomain: grafana.us.forteapps.net grafanaDomain: grafana.fortedigital.com
keycloakDomain: id.us.forteapps.net keycloakDomain: id.fortedigital.com
dotaiDomain: kubemcp.us.forteapps.net dotaiDomain: kubemcp.fortedigital.com
dotaiUiDomain: kubemcpui.us.forteapps.net dotaiUiDomain: kubemcpui.fortedigital.com
letsencryptEmail: danijels@gmail.com letsencryptEmail: danijel.simeunovic@fortedigital.com
trustedIPs: "10.0.0.0/16" trustedIPs: "172.16.1.0/24"
cloudProvider: tbd cloudProvider: upcloud

View File

@@ -96,10 +96,10 @@ You'll need read/write access to these repositories:
cd launchpad cd launchpad
``` ```
2. **helm-values** (Values repo) 2. **helm-prod-values** (Values repo)
```bash ```bash
git clone https://git.forteapps.net/Forte/helm-prod-values.git git clone https://git.forteapps.net/Forte/helm-prod-values.git
cd helm-values cd helm-prod-values
``` ```
3. **forte-helm** (Chart repo - read-only for most developers) 3. **forte-helm** (Chart repo - read-only for most developers)
@@ -175,13 +175,13 @@ npm run dev
│ - GitHub Actions builds image │ │ - GitHub Actions builds image │
│ - Pushes to container registry (GHCR, Docker Hub) │ │ - Pushes to container registry (GHCR, Docker Hub) │
│ - Tags with version (e.g., v2.0.4) │ │ - Tags with version (e.g., v2.0.4) │
│ - Updates helm-values repository with new tag │ │ - Updates helm-prod-values repository with new tag │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ Step 3: GitOps Sync (Automated) │ │ Step 3: GitOps Sync (Automated) │
│ - ArgoCD detects change in helm-values │ │ - ArgoCD detects change in helm-prod-values │
│ - Pulls updated configuration │ │ - Pulls updated configuration │
│ - Syncs to Kubernetes cluster │ │ - Syncs to Kubernetes cluster │
│ - Sends Slack notification on success/failure │ │ - Sends Slack notification on success/failure │
@@ -201,7 +201,7 @@ Our setup uses three repositories:
| Repository | Purpose | Who Edits | How Often | | Repository | Purpose | Who Edits | How Often |
|------------|---------|-----------|-----------| |------------|---------|-----------|-----------|
| **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely | | **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely |
| **helm-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes | | **helm-prod-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes |
| **launchpad** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app | | **launchpad** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app |
### Example: Deploying "myapp" ### Example: Deploying "myapp"
@@ -223,7 +223,7 @@ spec:
value: {{ .Values.app.port }} value: {{ .Values.app.port }}
``` ```
#### Repository: `helm-values` (Your App Config) #### Repository: `helm-prod-values` (Your App Config)
```yaml ```yaml
# myapp/values.yaml # myapp/values.yaml
# Your app's specific configuration # Your app's specific configuration
@@ -248,13 +248,13 @@ metadata:
namespace: argocd namespace: argocd
spec: spec:
sources: sources:
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://git.forteapps.net/Forte/forte-helm
path: forteapp path: forteapp
helm: helm:
valueFiles: valueFiles:
- $values/myapp/values.yaml - $values/myapp/values.yaml
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: git@github.com:fortedigital/helm-prod-values.git
ref: values ref: values
destination: destination:
@@ -316,10 +316,10 @@ Ensure your app repository has:
docker build -t ghcr.io/fortedigital/hello-world:${{ steps.version.outputs.VERSION }} . docker build -t ghcr.io/fortedigital/hello-world:${{ steps.version.outputs.VERSION }} .
docker push ghcr.io/fortedigital/hello-world:${{ steps.version.outputs.VERSION }} docker push ghcr.io/fortedigital/hello-world:${{ steps.version.outputs.VERSION }}
- name: Update helm-values - name: Update helm-prod-values
run: | run: |
git clone git@github.com:fortedigital/helm-values.git git clone git@github.com:fortedigital/helm-prod-values.git
cd helm-values cd helm-prod-values
mkdir -p hello-world mkdir -p hello-world
cat > hello-world/values.yaml <<EOF cat > hello-world/values.yaml <<EOF
app: app:
@@ -334,7 +334,7 @@ Ensure your app repository has:
### Step 2: Create Helm Values ### Step 2: Create Helm Values
Create a folder in `helm-values` repository: Create a folder in `helm-prod-values` repository:
```bash ```bash
cd ~/dev/k8s/helm-prod-values cd ~/dev/k8s/helm-prod-values
@@ -412,7 +412,7 @@ spec:
sources: sources:
# Source 1: Helm chart templates # Source 1: Helm chart templates
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://git.forteapps.net/Forte/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
@@ -420,7 +420,7 @@ spec:
- $values/hello-world/values.yaml - $values/hello-world/values.yaml
# Source 2: Helm values # Source 2: Helm values
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: git@github.com:fortedigital/helm-prod-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values
@@ -528,7 +528,7 @@ git push origin main
2. ✅ Builds new Docker image 2. ✅ Builds new Docker image
3. ✅ Tags with new version (e.g., `v20260316-143022`) 3. ✅ Tags with new version (e.g., `v20260316-143022`)
4. ✅ Pushes to container registry 4. ✅ Pushes to container registry
5. ✅ Updates `helm-values/myapp/values.yaml` with new tag 5. ✅ Updates `helm-prod-values/myapp/values.yaml` with new tag
6. ✅ ArgoCD detects change 6. ✅ ArgoCD detects change
7. ✅ Syncs new version to cluster 7. ✅ Syncs new version to cluster
8. ✅ Sends Slack notification 8. ✅ Sends Slack notification
@@ -683,7 +683,7 @@ git push
#### Step 4: Reference Secret in Application #### Step 4: Reference Secret in Application
Update your `helm-values/myapp/values.yaml`: Update your `helm-prod-values/myapp/values.yaml`:
```yaml ```yaml
app: app:
@@ -791,7 +791,7 @@ Three authentication modes are supported:
#### Step 1: Configure Helm Values #### Step 1: Configure Helm Values
```yaml ```yaml
# In helm-values/myapp/values.yaml # In helm-prod-values/myapp/values.yaml
auth: auth:
enabled: true enabled: true
type: token # Token mode (default) type: token # Token mode (default)
@@ -913,7 +913,7 @@ rm private/myapp-auth-oidc.yaml
#### Step 3: Configure Helm Values #### Step 3: Configure Helm Values
```yaml ```yaml
# In helm-values/myapp/values.yaml # In helm-prod-values/myapp/values.yaml
auth: auth:
enabled: true enabled: true
type: oidc # OIDC mode type: oidc # OIDC mode
@@ -1049,7 +1049,7 @@ policies.forteapps.io/auth-image-version: "v1.2.3"
#### Example 1: Internal API with Token Auth #### Example 1: Internal API with Token Auth
```yaml ```yaml
# helm-values/internal-api/values.yaml # helm-prod-values/internal-api/values.yaml
app: app:
image: image:
repository: ghcr.io/company/internal-api repository: ghcr.io/company/internal-api
@@ -1077,7 +1077,7 @@ curl -H "Authorization: Bearer d4f88f..." \
#### Example 2: User-Facing App with OIDC #### Example 2: User-Facing App with OIDC
```yaml ```yaml
# helm-values/web-app/values.yaml # helm-prod-values/web-app/values.yaml
app: app:
image: image:
repository: ghcr.io/company/web-app repository: ghcr.io/company/web-app
@@ -1112,7 +1112,7 @@ kubectl create secret generic auth-oidc \
#### Example 3: MCP Server with OAuth 2.0 #### Example 3: MCP Server with OAuth 2.0
```yaml ```yaml
# helm-values/mcp-server/values.yaml # helm-prod-values/mcp-server/values.yaml
app: app:
image: image:
repository: ghcr.io/company/mcp-server repository: ghcr.io/company/mcp-server
@@ -1136,7 +1136,7 @@ The MCP auth mode implements RFC 9728 (OAuth 2.0 Protected Resource Metadata) fo
#### Example 4: Disabling Authentication #### Example 4: Disabling Authentication
```yaml ```yaml
# helm-values/public-api/values.yaml # helm-prod-values/public-api/values.yaml
auth: auth:
enabled: false # No authentication enabled: false # No authentication
@@ -1500,7 +1500,7 @@ kubectl exec -n myapp <pod-name> -- env
# Check if secrets exist # Check if secrets exist
kubectl get secrets -n myapp kubectl get secrets -n myapp
# Increase resources in helm-values # Increase resources in helm-prod-values
vim ~/dev/k8s/helm-prod-values/myapp/values.yaml vim ~/dev/k8s/helm-prod-values/myapp/values.yaml
``` ```
@@ -1649,7 +1649,7 @@ If you're stuck:
### Configuration Management ### Configuration Management
✅ **DO**: ✅ **DO**:
- Keep configuration in `helm-values` repository - Keep configuration in `helm-prod-values` repository
- Use environment variables for config - Use environment variables for config
- Document what each value does - Document what each value does
- Use reasonable resource limits - Use reasonable resource limits

View File

@@ -12,11 +12,11 @@
## Overview ## 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 ### Key Characteristics
- **Environment**: Production (internal use only) - **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 - **GitOps Tool**: ArgoCD
- **Deployment Pattern**: App-of-Apps - **Deployment Pattern**: App-of-Apps
- **Secret Management**: Sealed Secrets (kubeseal) - **Secret Management**: Sealed Secrets (kubeseal)
@@ -47,7 +47,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
│ │ │ │ │ │
│ │ │ │ │ │
└────────► Update image tag ─┴──────────────────────────┘ └────────► Update image tag ─┴──────────────────────────┘
in helm-values │ in helm-prod-values │
┌────────────────────────────────┐ ┌────────────────────────────────┐
@@ -63,7 +63,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Kubernetes Clusters │ │ Kubernetes Clusters │
│ (UpCloud: upc-dev, upc-prod) │ (UpCloud, AWS, Azure, GCP)
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ ArgoCD │ │ │ │ ArgoCD │ │
@@ -131,26 +131,22 @@ launchpad/
│ │ ├── renovate.yaml │ │ ├── renovate.yaml
│ │ ├── ... # All other Application manifests │ │ ├── ... # All other Application manifests
│ │ └── secrets.yaml │ │ └── secrets.yaml
│ ├── overlays/ # Per-cluster overrides │ ├── overlays/ # Per-cluster Kustomize overrides
│ │ ├── upc-dev/ # UpCloud Dev (uses base as-is) │ │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
│ │ ── upc-prod/ # UpCloud Prod (patches value paths) │ │ ── upc-prod/ # UpCloud Prod (patches value paths)
│ │ ├── eks-dev/ # AWS EKS Dev
│ │ ├── eks-prod/ # AWS EKS Prod
│ │ ├── aks-dev/ # Azure AKS Dev
│ │ ├── aks-prod/ # Azure AKS Prod
│ │ ├── gke-dev/ # GCP GKE Dev
│ │ └── gke-prod/ # GCP GKE Prod
│ ├── dashboards/ # Grafana dashboard ConfigMaps │ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides for infra │ └── values/ # Helm value overrides for infra
│ ├── base/ # Shared values (all clusters) │ ├── base/ # Cloud-agnostic shared values
│ ├── traefik-values.yaml ├── upc-{dev,prod}/ # UpCloud: storage class, LB, pricing
│ ├── keycloak-values.yaml ├── aws-{dev,prod}/ # AWS: gp3, NLB, CUR pricing
│ ├── grafana-values.yaml ├── aks-{dev,prod}/ # Azure: managed-csi-premium, Standard LB
│ ├── prometheus-values.yaml └── gcp-{dev,prod}/ # GCP: premium-rwo, L4 LB
│ │ ├── 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
├── apps/ # Business Application ArgoCD manifests (Kustomize) ├── apps/ # Business Application ArgoCD manifests (Kustomize)
│ ├── base/ # Base app manifests │ ├── base/ # Base app manifests
@@ -184,7 +180,7 @@ launchpad/
--- ---
### 2. **Helm Charts Repository** ### 2. **Helm Charts Repository**
**Repository**: `https://github.com/fortedigital/forte-helm` **Repository**: `https://git.forteapps.net/Forte/forte-helm`
**Purpose**: Reusable Helm chart templates for Forte applications **Purpose**: Reusable Helm chart templates for Forte applications
**Location**: `C:\dev\k8s\forte-helm` **Location**: `C:\dev\k8s\forte-helm`
@@ -218,7 +214,7 @@ forte-helm/
--- ---
### 3. **Helm Values Repository** ### 3. **Helm Values Repository**
**Repository**: `git@github.com:fortedigital/helm-values.git` **Repository**: `git@github.com:fortedigital/helm-prod-values.git`
**Purpose**: Environment-specific configuration for each application **Purpose**: Environment-specific configuration for each application
**Location**: `C:\dev\k8s\helm-prod-values` **Location**: `C:\dev\k8s\helm-prod-values`
@@ -228,8 +224,6 @@ helm-prod-values/
│ └── values.yaml # MCP 10X configuration │ └── values.yaml # MCP 10X configuration
├── musicman/ ├── musicman/
│ └── values.yaml # Music Man configuration │ └── values.yaml # Music Man configuration
├── mcpcoder/
│ └── values.yaml # MCP Coder configuration
└── argocd-mcp/ └── argocd-mcp/
└── values.yaml # ArgoCD MCP configuration └── values.yaml # ArgoCD MCP configuration
``` ```
@@ -279,7 +273,7 @@ app-repository/
2. Build Docker image 2. Build Docker image
3. Tag with version (e.g., `v2.0.4`) 3. Tag with version (e.g., `v2.0.4`)
4. Push to container registry (GHCR, Docker Hub, etc.) 4. Push to container registry (GHCR, Docker Hub, etc.)
5. Update image tag in `helm-values` repository 5. Update image tag in `helm-prod-values` repository
6. ArgoCD detects change and syncs automatically 6. ArgoCD detects change and syncs automatically
--- ---
@@ -289,7 +283,7 @@ app-repository/
### The App-of-Apps Pattern ### 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, eks-prod, gke-dev)
├── infrastructure-apps (manages infra/) ├── infrastructure-apps (manages infra/)
│ ├── cluster-resources-application │ ├── cluster-resources-application
@@ -340,13 +334,13 @@ Applications like `mcp10x` and `musicman` use multiple sources:
```yaml ```yaml
spec: spec:
sources: sources:
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://git.forteapps.net/Forte/forte-helm
path: forteapp # Helm chart templates path: forteapp # Helm chart templates
helm: helm:
valueFiles: valueFiles:
- $values/mcp10x/values.yaml # Reference to second source - $values/mcp10x/values.yaml # Reference to second source
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: git@github.com:fortedigital/helm-prod-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values # Named reference ref: values # Named reference
``` ```
@@ -379,6 +373,15 @@ patches:
value: $values/infra/values/upc-prod/traefik-values.yaml 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**: **Benefits**:
- Single source of truth for Application definitions - Single source of truth for Application definitions
- Cluster-specific values isolated per overlay - Cluster-specific values isolated per overlay
@@ -414,8 +417,8 @@ jobs:
- name: Update Helm values - name: Update Helm values
run: | run: |
git clone git@github.com:fortedigital/helm-values.git git clone git@github.com:fortedigital/helm-prod-values.git
cd helm-values/app cd helm-prod-values/app
sed -i "s/tag: .*/tag: $VERSION/" values.yaml sed -i "s/tag: .*/tag: $VERSION/" values.yaml
git commit -am "Update app to $VERSION" git commit -am "Update app to $VERSION"
git push git push
@@ -432,7 +435,7 @@ jobs:
- Syncs application to cluster - Syncs application to cluster
2. **Helm Values Change**: 2. **Helm Values Change**:
- CI/CD updates `helm-values/myapp/values.yaml` - CI/CD updates `helm-prod-values/myapp/values.yaml`
- ArgoCD detects change - ArgoCD detects change
- Pulls new Helm chart with updated values - Pulls new Helm chart with updated values
- Applies to cluster - Applies to cluster
@@ -639,7 +642,7 @@ Notifications include:
✅ **DO**: ✅ **DO**:
- Follow the `forteapp` chart pattern - Follow the `forteapp` chart pattern
- Use semantic versioning for image tags - Use semantic versioning for image tags
- Update helm-values via CI/CD - Update helm-prod-values via CI/CD
- Test locally with Docker Compose - Test locally with Docker Compose
- Document environment variables - Document environment variables
@@ -660,6 +663,6 @@ Notifications include:
--- ---
**Last Updated**: 2026-03-16 **Last Updated**: 2026-04-22
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Questions?**: Contact #platform-support on Slack **Questions?**: Contact #platform-support on Slack

View File

@@ -37,7 +37,7 @@ Bootstrap a new cluster from scratch:
#### Prerequisites #### 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 2. **kubectl configured** with admin access
3. **Repositories cloned** locally 3. **Repositories cloned** locally
@@ -54,11 +54,13 @@ kubectl get nodes
git clone https://git.forteapps.net/Forte/launchpad git clone https://git.forteapps.net/Forte/launchpad
cd launchpad cd launchpad
# 2. Set cluster name (optional) # 2. Run bootstrap script with cluster target
export CLUSTER_NAME="prod-cluster-01" # Available clusters: upc-dev, upc-prod, eks-dev, eks-prod,
# aks-dev, aks-prod, gke-dev, gke-prod
./bootstrap.sh upc-dev
# 3. Run bootstrap script # Cluster config is loaded from clusters/<cluster>.yaml
./bootstrap.sh # (cloudProvider, trustedIPs, domain, etc.)
``` ```
**What Happens:** **What Happens:**
@@ -85,7 +87,8 @@ kubectl get applications -n argocd
1. **Configure DNS** for ingress domains: 1. **Configure DNS** for ingress domains:
- `argocd.127.0.0.1.nip.io` (local dev) - `argocd.127.0.0.1.nip.io` (local dev)
- `*.forteapps.net` (production) - `*.forteapps.net` (dev)
- `*.fortedigital.com` (production)
2. **Verify Let's Encrypt certificates**: 2. **Verify Let's Encrypt certificates**:
```bash ```bash
@@ -107,7 +110,7 @@ kubectl get applications -n argocd
### ArgoCD Repository Access Setup ### ArgoCD Repository Access Setup
ArgoCD needs SSH access to private Git repositories to pull manifests and Helm values. This section covers setting up deploy keys for GitHub repositories. ArgoCD needs SSH access to private Git repositories to pull manifests and Helm values. This section covers setting up deploy keys for Gitea repositories.
#### Why Deploy Keys? #### Why Deploy Keys?
@@ -119,7 +122,7 @@ ArgoCD needs SSH access to private Git repositories to pull manifests and Helm v
#### Prerequisites #### Prerequisites
- kubectl access to the cluster - kubectl access to the cluster
- Write access to the GitHub repository - Write access to the Gitea repository
- ArgoCD installed and running - ArgoCD installed and running
#### Setup Procedure #### Setup Procedure
@@ -138,16 +141,16 @@ ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key
This creates two files: This creates two files:
- `argocd-deploy-key` - Private key (keep secret) - `argocd-deploy-key` - Private key (keep secret)
- `argocd-deploy-key.pub` - Public key (add to GitHub) - `argocd-deploy-key.pub` - Public key (add to Gitea)
**Step 2: Add Public Key to GitHub** **Step 2: Add Public Key to Gitea**
1. Copy the public key: 1. Copy the public key:
```bash ```bash
cat argocd-deploy-key.pub cat argocd-deploy-key.pub
``` ```
2. Go to GitHub repository settings: 2. Go to Gitea repository settings:
- Navigate to: `https://git.forteapps.net/Forte/launchpad/settings/keys` - Navigate to: `https://git.forteapps.net/Forte/launchpad/settings/keys`
- Or: Repository → Settings → Deploy keys - Or: Repository → Settings → Deploy keys
@@ -157,12 +160,12 @@ This creates two files:
- ☐ Allow write access (leave unchecked - read-only is sufficient) - ☐ Allow write access (leave unchecked - read-only is sufficient)
- Click **"Add key"** - Click **"Add key"**
4. Repeat for the `helm-values` repository if it's private: 4. Repeat for the `helm-prod-values` repository if it's private:
```bash ```bash
# Generate separate key for helm-values repo # Generate separate key for helm-prod-values repo
ssh-keygen -t ed25519 -C "argocd-deploy-key-helm-values" -f argocd-helm-values-key -N "" ssh-keygen -t ed25519 -C "argocd-deploy-key-helm-prod-values" -f argocd-helm-prod-values-key -N ""
# Add to: https://github.com/fortedigital/helm-values/settings/keys # Add to: https://git.forteapps.net/Forte/helm-prod-values/settings/keys
``` ```
**Step 3: Create Kubernetes Secret** **Step 3: Create Kubernetes Secret**
@@ -270,7 +273,7 @@ rm /tmp/test-repo-access.yaml
# Generate new key # Generate new key
ssh-keygen -t ed25519 -C "argocd-deploy-key-$(date +%Y%m)" -f argocd-new-key -N "" ssh-keygen -t ed25519 -C "argocd-deploy-key-$(date +%Y%m)" -f argocd-new-key -N ""
# Add new public key to GitHub (keep old key for now) # Add new public key to Gitea (keep old key for now)
# Update Kubernetes secret # Update Kubernetes secret
kubectl create secret generic repo-launchpad \ kubectl create secret generic repo-launchpad \
@@ -278,7 +281,7 @@ rm /tmp/test-repo-access.yaml
--namespace=argocd \ --namespace=argocd \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
# Test access, then remove old deploy key from GitHub # Test access, then remove old deploy key from Gitea
# Clean up # Clean up
shred -u argocd-new-key shred -u argocd-new-key
@@ -289,7 +292,7 @@ rm /tmp/test-repo-access.yaml
# List all repository secrets # List all repository secrets
kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository
# Review deploy keys in GitHub # Review deploy keys in Gitea
# Visit: https://git.forteapps.net/Forte/launchpad/settings/keys # Visit: https://git.forteapps.net/Forte/launchpad/settings/keys
``` ```
@@ -312,16 +315,16 @@ kubectl get secret repo-launchpad -n argocd -o yaml | grep argocd.argoproj.io/se
# Check ArgoCD application controller logs # Check ArgoCD application controller logs
kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied" kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied"
# Verify deploy key is added to GitHub # Verify deploy key is added to Gitea
# Visit: https://git.forteapps.net/Forte/launchpad/settings/keys # Visit: https://git.forteapps.net/Forte/launchpad/settings/keys
``` ```
**Issue: "Host key verification failed"** **Issue: "Host key verification failed"**
```bash ```bash
# Add GitHub to known_hosts # Add Gitea to known_hosts
kubectl exec -n argocd deployment/argocd-repo-server -- \ kubectl exec -n argocd deployment/argocd-repo-server -- \
ssh-keyscan github.com >> ~/.ssh/known_hosts ssh-keyscan git.forteapps.net >> ~/.ssh/known_hosts
# Or disable strict host key checking (less secure) # Or disable strict host key checking (less secure)
kubectl patch secret repo-launchpad -n argocd \ kubectl patch secret repo-launchpad -n argocd \
@@ -346,16 +349,16 @@ kubectl rollout restart deployment argocd-application-controller -n argocd
#### Multiple Repository Setup #### Multiple Repository Setup
For the three-repository pattern (launchpad, forte-helm, helm-values): For the three-repository pattern (launchpad, forte-helm, helm-prod-values):
```bash ```bash
# 1. launchpad (main config repo) # 1. launchpad (main config repo)
ssh-keygen -t ed25519 -C "argocd-launchpad" -f key-sturdy -N "" ssh-keygen -t ed25519 -C "argocd-launchpad" -f key-sturdy -N ""
# Add key-sturdy.pub to: https://git.forteapps.net/Forte/launchpad/settings/keys # Add key-sturdy.pub to: https://git.forteapps.net/Forte/launchpad/settings/keys
# 2. helm-values (private values repo) # 2. helm-prod-values (private values repo)
ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N "" ssh-keygen -t ed25519 -C "argocd-helm-prod-values" -f key-helm-prod-values -N ""
# Add key-helm-values.pub to: https://github.com/fortedigital/helm-values/settings/keys # Add key-helm-prod-values.pub to: https://git.forteapps.net/Forte/helm-prod-values/settings/keys
# 3. forte-helm (private helm charts repo) # 3. forte-helm (private helm charts repo)
@@ -366,14 +369,14 @@ kubectl create secret generic repo-launchpad \
kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \ kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \
kubectl apply -f - kubectl apply -f -
kubectl create secret generic repo-helm-values \ kubectl create secret generic repo-helm-prod-values \
--from-file=sshPrivateKey=key-helm-values \ --from-file=sshPrivateKey=key-helm-prod-values \
--namespace=argocd --dry-run=client -o yaml | \ --namespace=argocd --dry-run=client -o yaml | \
kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \ kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \
kubectl apply -f - kubectl apply -f -
# Clean up keys # Clean up keys
shred -u key-sturdy key-helm-values shred -u key-sturdy key-helm-prod-values
``` ```
#### Converting HTTPS to SSH #### Converting HTTPS to SSH
@@ -390,7 +393,7 @@ If you're currently using HTTPS and want to switch to SSH:
# repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git # repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
# 3. Update and commit # 3. Update and commit
find . -name "*.yaml" -type f -exec sed -i 's|https://github.com/fortedigital/|git@github.com:fortedigital/|g' {} + find . -name "*.yaml" -type f -exec sed -i 's|https://git.forteapps.net/Forte/|git@git.forteapps.net:Forte/|g' {} +
git add . git add .
git commit -m "Switch from HTTPS to SSH for repository access" git commit -m "Switch from HTTPS to SSH for repository access"
@@ -494,7 +497,7 @@ spec:
See [Developer Guide](DEVELOPER-GUIDE.md#deploying-your-first-application) for detailed steps. See [Developer Guide](DEVELOPER-GUIDE.md#deploying-your-first-application) for detailed steps.
**Quick checklist:** **Quick checklist:**
- [ ] Create `helm-values/myapp/values.yaml` - [ ] Create `helm-prod-values/myapp/values.yaml`
- [ ] Create `apps/myapp.yaml` in config repo - [ ] Create `apps/myapp.yaml` in config repo
- [ ] Create SealedSecret if needed - [ ] Create SealedSecret if needed
- [ ] Commit and push changes - [ ] Commit and push changes
@@ -559,7 +562,7 @@ kubectl scale deployment myapp -n myapp --replicas=3
#### GitOps Scaling #### GitOps Scaling
Update `helm-values/myapp/values.yaml`: Update `helm-prod-values/myapp/values.yaml`:
```yaml ```yaml
app: app:
@@ -573,7 +576,7 @@ Commit and push - ArgoCD will sync.
Enable Horizontal Pod Autoscaler: Enable Horizontal Pod Autoscaler:
```yaml ```yaml
# In helm-values/myapp/values.yaml # In helm-prod-values/myapp/values.yaml
app: app:
hpa: hpa:
enabled: true enabled: true
@@ -622,7 +625,7 @@ kubectl rollout undo deployment myapp -n myapp
#### Option 3: Change Image Tag #### Option 3: Change Image Tag
```bash ```bash
# Edit helm-values # Edit helm-prod-values
cd ~/dev/k8s/helm-prod-values cd ~/dev/k8s/helm-prod-values
vim myapp/values.yaml vim myapp/values.yaml
@@ -642,7 +645,7 @@ git push
#### Update Resource Limits #### Update Resource Limits
```yaml ```yaml
# In helm-values/myapp/values.yaml # In helm-prod-values/myapp/values.yaml
app: app:
resources: resources:
requests: requests:
@@ -656,7 +659,7 @@ app:
#### Enable Database #### Enable Database
```yaml ```yaml
# In helm-values/myapp/values.yaml # In helm-prod-values/myapp/values.yaml
db: db:
enabled: true enabled: true
persistence: persistence:
@@ -1261,13 +1264,21 @@ spec:
### Backup Strategy ### Backup Strategy
**Current State**: No automated backups **Current State**: Gitea daily backups to S3-compatible storage
**What Needs Backup**: **What Is Backed Up**:
- ❌ Cluster state (not backed up - recreate via GitOps) - ✅ Gitea repositories + database: Daily CronJob (`cluster-resources/gitea-backup-cronjob.yaml`) uploads to S3-compatible storage with 7-day retention
- ❌ Persistent volumes (currently not critical) - ✅ Git repositories: Full cluster config recoverable from Git
- ✅ Git repositories (GitHub provides backup) - ⚠️ Secrets: Sealed secrets in Git; unseal keys need safekeeping
- ⚠️ 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-eks.sh` (MinIO CLI, S3-compatible)
- Azure: `scripts/gitea-backup-aks.sh` (Azure CLI + Blob Storage)
- GCP: `scripts/gitea-backup-gke.sh` (gsutil + GCS)
### Cluster Rebuild ### Cluster Rebuild
@@ -1369,6 +1380,9 @@ kubectl get pods -n argocd
```bash ```bash
# UpCloud: Upgrade via control panel or CLI # 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 # After upgrade, verify cluster
kubectl version kubectl version
@@ -1506,18 +1520,35 @@ git push
### Multi-Cluster Setup ### 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-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` - **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod`
Each cluster has its own: **Cloud-ready templates (fill in `clusters/*.yaml` before use):**
- Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml` - **eks-dev** / **eks-prod**: AWS EKS with NLB, gp3 storage, AWS CUR pricing
- Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/` - **aks-dev** / **aks-prod**: Azure AKS with Standard LB, managed-csi-premium storage
- Sealed secrets: `secrets/upc-dev/` (others as needed) - **gke-dev** / **gke-prod**: GCP GKE with L4 LB, premium-rwo storage
- Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/`
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/eks-staging/`) with patches that swap the value file paths, and a matching `clusters/eks-staging.yaml`.
### Blue-Green Deployments ### Blue-Green Deployments
@@ -1561,7 +1592,7 @@ git push
kubectl scale deployment myapp -n myapp --replicas=0 kubectl scale deployment myapp -n myapp --replicas=0
# Update Git # Update Git
vim helm-values/myapp/values.yaml vim helm-prod-values/myapp/values.yaml
# Set replicaCount: 0 # Set replicaCount: 0
git commit -am "Scale down myapp for maintenance" git commit -am "Scale down myapp for maintenance"
git push git push
@@ -1634,7 +1665,7 @@ echo "Remember to delete: $SECRET_FILE"
- [ ] Application code repository created - [ ] Application code repository created
- [ ] Dockerfile created and tested - [ ] Dockerfile created and tested
- [ ] GitHub Actions workflow configured - [ ] Gitea Actions workflow configured
- [ ] Helm values created in `helm-prod-values/` - [ ] Helm values created in `helm-prod-values/`
- [ ] ArgoCD application manifest created in `apps/` - [ ] ArgoCD application manifest created in `apps/`
- [ ] Secrets created and sealed - [ ] Secrets created and sealed
@@ -1660,6 +1691,6 @@ echo "Remember to delete: $SECRET_FILE"
--- ---
**Last Updated**: 2026-03-16 **Last Updated**: 2026-04-22
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Emergency Contact**: #platform-support on Slack **Emergency Contact**: #platform-support on Slack

View File

@@ -180,7 +180,7 @@ Reference for:
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Kubernetes Clusters (UpCloud: upc-dev, upc-prod) │ Kubernetes Clusters (UpCloud, AWS, Azure, GCP)
│ ┌──────────────────────────────────────────────────────┐ │ │ ┌──────────────────────────────────────────────────────┐ │
│ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │ │ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │
│ ├──────────────────────────────────────────────────────┤ │ │ ├──────────────────────────────────────────────────────┤ │
@@ -194,7 +194,7 @@ Reference for:
### Key Technologies ### Key Technologies
- **GitOps**: ArgoCD - **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 - **Ingress**: Traefik v2
- **Certificates**: Cert-Manager + Let's Encrypt - **Certificates**: Cert-Manager + Let's Encrypt
- **Policies**: Kyverno - **Policies**: Kyverno
@@ -299,11 +299,16 @@ docs/
## 🔄 Documentation Versions ## 🔄 Documentation Versions
**Current Version**: 1.0.0 **Current Version**: 1.0.0
**Last Updated**: 2026-03-16 **Last Updated**: 2026-04-22
**Maintained By**: Platform Team **Maintained By**: Platform Team
### Changelog ### 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 - **v1.0.0 (2026-03-16)**: Initial comprehensive documentation release
- GitOps Architecture guide - GitOps Architecture guide
- Developer Onboarding guide - Developer Onboarding guide

View File

@@ -9,6 +9,7 @@
- [Kyverno Policies](#kyverno-policies) - [Kyverno Policies](#kyverno-policies)
- [Configuration Reference](#configuration-reference) - [Configuration Reference](#configuration-reference)
- [API Endpoints](#api-endpoints) - [API Endpoints](#api-endpoints)
- [Cloud Overlay Pattern](#cloud-overlay-pattern)
- [Glossary](#glossary) - [Glossary](#glossary)
--- ---
@@ -19,9 +20,10 @@
| Component | Value | | Component | Value |
|-----------|-------| |-----------|-------|
| **Provider** | UpCloud Managed Kubernetes | | **Provider** | Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) |
| **Environment** | Production (internal use) | | **Environment** | Dev + Production per cloud |
| **Cluster Count** | Multi-cluster (upc-dev, upc-prod) | | **Active clusters** | UpCloud (upc-dev, upc-prod) |
| **Cloud-ready templates** | EKS, AKS, GKE (dev + prod each) |
| **GitOps Tool** | ArgoCD | | **GitOps Tool** | ArgoCD |
| **Ingress Controller** | Traefik v2 | | **Ingress Controller** | Traefik v2 |
| **Certificate Management** | Cert-Manager + Let's Encrypt | | **Certificate Management** | Cert-Manager + Let's Encrypt |
@@ -42,7 +44,7 @@ Internet
[DNS: *.forteapps.net] [DNS: *.forteapps.net]
[UpCloud LoadBalancer] [Cloud Load Balancer]
[Traefik Ingress Controller] [Traefik Ingress Controller]
@@ -92,16 +94,34 @@ launchpad/
│ ├── sealedsecrets.yaml │ ├── sealedsecrets.yaml
│ ├── secrets.yaml │ ├── secrets.yaml
│ ├── renovate.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/ │ └── values/
│ ├── argocd-values.yaml │ ├── base/ # Cloud-agnostic Helm values
├── prometheus-values.yaml │ ├── gitea-values.yaml
├── grafana-values.yaml │ ├── opencost-values.yaml
├── loki-values.yaml │ ├── prometheus-values.yaml
├── tempo-values.yaml │ └── ...
│ ├── gitea-values.yaml │ ├── upc-dev/ # UpCloud dev overlay values
├── gitea-actions-values.yaml │ ├── traefik-values.yaml
├── fluent-bit-values.yaml │ ├── keycloak-values.yaml
└── renovate-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
├── apps/ # Business applications ├── apps/ # Business applications
│ ├── mcp10x.yaml │ ├── mcp10x.yaml
@@ -135,6 +155,15 @@ launchpad/
│ ├── mcp10x-credentials-sealed.yaml │ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.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) ├── private/ # Local-only (Git-ignored)
│ ├── *.yaml │ ├── *.yaml
│ └── *.sh │ └── *.sh
@@ -190,7 +219,7 @@ spec:
### Helm Charts Repository: `forte-helm` ### Helm Charts Repository: `forte-helm`
**URL**: `https://github.com/fortedigital/forte-helm` **URL**: `https://git.forteapps.net/Forte/forte-helm`
#### Chart: `forteapp` #### Chart: `forteapp`
@@ -337,20 +366,18 @@ configmap: [] # Application ConfigMap key-value pairs
--- ---
### Helm Values Repository: `helm-values` ### Helm Values Repository: `helm-prod-values`
**URL**: `https://github.com/fortedigital/helm-values.git` **URL**: `https://git.forteapps.net/Forte/helm-prod-values.git`
#### Structure #### Structure
``` ```
helm-values/ helm-prod-values/
├── mcp10x/ ├── mcp10x/
│ └── values.yaml │ └── values.yaml
├── musicman/ ├── musicman/
│ └── values.yaml │ └── values.yaml
├── mcpcoder/
│ └── values.yaml
└── argocd-mcp/ └── argocd-mcp/
└── values.yaml └── values.yaml
``` ```
@@ -526,14 +553,14 @@ spec:
# Multi-source configuration # Multi-source configuration
sources: sources:
- repoURL: https://github.com/fortedigital/forte-helm - repoURL: https://git.forteapps.net/Forte/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/<app-name>/values.yaml - $values/<app-name>/values.yaml
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: git@github.com:fortedigital/helm-prod-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values
@@ -817,12 +844,21 @@ postgresql:
**Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`). Protocol mapper: `email_verified` hardcoded claim (`true`, boolean) on ID token, Access token, and Userinfo. **Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`). Protocol mapper: `email_verified` hardcoded claim (`true`, boolean) on ID token, Access token, and Userinfo.
**External User Sync**: Disabled (`cron.sync_external_users.ENABLED: false`). This Gitea cron job is designed for LDAP and deactivates OIDC-only users because it cannot enumerate them — causing "Sign-in prohibited" errors after the sync runs.
**Email Notifications**: Enabled (`ENABLE_NOTIFY_MAIL: true`). SMTP credentials injected via `gitea-smtp-secret` using `additionalConfigFromEnvs` with `GITEA__mailer__USER` / `GITEA__mailer__PASSWD` environment variables.
**Auto-Watch**: Disabled (`AUTO_WATCH_ON_CHANGES: false`, `AUTO_WATCH_NEW_REPOS: false`). Prevents contributors from being auto-subscribed to repo notifications on push, reducing email noise from CI bots (e.g., ai-review PR comments). Users who were already watching before this change need to manually unwatch or switch to "Only participating".
**Endpoints**: **Endpoints**:
- Web UI: `https://git.forteapps.net` - Web UI: `https://git.forteapps.net`
- SSH: port 22 (ClusterIP) - SSH: port 22 (ClusterIP)
- Metrics: `/metrics` (Prometheus scrape) - Metrics: `/metrics` (Prometheus scrape)
**Secrets**: `gitea-credentials` (SealedSecret) containing `admin-password`, `postgres-password`, `secret` (OIDC client secret) **Secrets**:
- `gitea-credentials` (SealedSecret) — admin password
- `gitea-oidc-credentials` (registrar-managed) — OIDC client ID + secret
- `gitea-smtp-secret` (SealedSecret) — SMTP username + password
### Gitea Actions Runners ### Gitea Actions Runners
@@ -871,6 +907,84 @@ dind:
- Gitea admin panel (`/admin/runners`) — runners show as Online - Gitea admin panel (`/admin/runners`) — runners show as Online
- Create test workflow in `.gitea/workflows/test.yml` — job executes - Create test workflow in `.gitea/workflows/test.yml` — job executes
### AI Code Review (ai-review)
**Type**: Gitea Actions workflow (`.gitea/workflows/ai-review.yaml`)
**Trigger**: `pull_request` events (`opened`, `synchronize`)
**Runner**: `ubuntu-latest` (container: `nikitafilonov/ai-review:latest`)
**Purpose**: Automated AI-powered code review on pull requests using Claude (Anthropic). Posts inline comments on changed lines and a PR summary comment highlighting infrastructure impact.
**Architecture**:
- Uses [xai-review](https://github.com/nicktechnologies/xai-review) Docker image
- Shared configuration and prompts live in the `shared-prompts` Git submodule (→ `Forte/ai-review-prompts`)
- Review mode: `ONLY_ADDED_WITH_CONTEXT` — reviews only new/changed lines plus surrounding context (token-efficient)
- Agent mode: disabled (one-shot review, no multi-turn reasoning)
- LLM: Claude Sonnet (`claude-sonnet-4-20250514`)
**Shared Prompts Structure** (submodule: `Forte/ai-review-prompts`):
```
shared-prompts/
base/
security.md # org-wide security rules (all profiles)
iac/
.ai-review.yaml # IaC/GitOps profile config
inline.md # inline review prompt
summary.md # PR summary prompt
# future profiles: backend/, frontend/, etc.
```
**Configuration** (`shared-prompts/iac/.ai-review.yaml`):
```yaml
llm:
provider: CLAUDE
model: claude-sonnet-4-20250514
vcs:
provider: GITEA
review:
mode: ONLY_ADDED_WITH_CONTEXT
agent:
enabled: false
prompt:
inline_prompt_files: # concatenated in order
- ./shared-prompts/base/security.md
- ./shared-prompts/iac/inline.md
summary_prompt_files:
- ./shared-prompts/iac/summary.md
ignore:
- "*.sealed.yaml"
- "*.lock"
- "docs/**"
```
**Custom Prompts** (IaC profile):
- `shared-prompts/base/security.md` — org-wide security rules, concatenated before every inline review prompt
- `shared-prompts/iac/inline.md` — IaC-specific inline review (YAML, Helm, K8s manifests, shell scripts), max 7 comments
- `shared-prompts/iac/summary.md` — PR summary: affected services/namespaces, infrastructure impact, security flags
**Prompt composition**: ai-review does not support Jinja includes. Instead, list multiple files under `inline_prompt_files` / `summary_prompt_files` — they are concatenated in order with double newlines.
**Adding a new profile**: Create a new directory (e.g., `backend/`) with its own `.ai-review.yaml`, `inline.md`, and `summary.md`. The `inline_prompt_files` list should include `base/security.md` first, then the profile-specific prompt. Reference it in the consuming repo's workflow: `AI_REVIEW_CONFIG_FILE_YAML=./shared-prompts/backend/.ai-review.yaml`
**Required Secrets** (configure in Gitea repo or org settings):
| Secret | Purpose |
|--------|---------|
| `ANTHROPIC_API_KEY` | Claude API key (from Anthropic console) |
| `AI_REVIEW_TOKEN` | Gitea API token with `write:repository` + `read:repository` scopes (use a bot/service account) |
**Setup Steps**:
1. Create a Gitea bot/service account and generate an API token with `write:repository` + `read:repository` scopes
2. Add `AI_REVIEW_TOKEN` secret in Gitea repo settings → Actions → Secrets
3. Add `ANTHROPIC_API_KEY` secret with your Anthropic API key
4. Ensure the `shared-prompts` submodule is initialized (`git submodule update --init`)
5. Push the workflow file — it triggers automatically on PR creation/update
**Verification**:
- Open a PR with infrastructure changes → workflow runs → inline comments + summary appear
- Check Gitea Actions tab for workflow run status and logs
- Monitor Anthropic usage dashboard for token consumption
### Keycloak Client Registrar ### Keycloak Client Registrar
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`) **Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
@@ -1465,14 +1579,22 @@ Recommended resource allocation:
### Storage Classes ### 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 ```yaml
# Example: base values omit storageClass (set in per-cluster overlay)
persistence: persistence:
enabled: true enabled: true
storageClass: "" # Uses default
accessMode: ReadWriteOnce accessMode: ReadWriteOnce
size: 5Gi size: 5Gi
# storageClass set by infra/values/{cluster}/gitea-values.yaml
``` ```
--- ---
@@ -1536,6 +1658,87 @@ 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 ## Glossary
### Terms ### Terms
@@ -1668,6 +1871,6 @@ team: platform
--- ---
**Last Updated**: 2026-04-16 **Last Updated**: 2026-04-22
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Version**: 1.0.0 **Version**: 1.0.0

View File

@@ -22,6 +22,7 @@ spec:
releaseName: gitea releaseName: gitea
valueFiles: valueFiles:
- $values/infra/values/base/gitea-values.yaml - $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 - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD

View File

@@ -17,6 +17,7 @@ resources:
- secrets.yaml - secrets.yaml
- gitea.yaml - gitea.yaml
- gitea-actions.yaml - gitea-actions.yaml
- opencost.yaml
- renovate.yaml - renovate.yaml
- tempo.yaml - tempo.yaml
- grafana-dashboards.yaml - grafana-dashboards.yaml

View File

@@ -21,7 +21,8 @@ spec:
helm: helm:
releaseName: opencost releaseName: opencost
valueFiles: valueFiles:
- $values/infra/values/opencost-values.yaml - $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: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD

View File

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

View File

@@ -0,0 +1,68 @@
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

@@ -0,0 +1,68 @@
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

@@ -0,0 +1,68 @@
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

@@ -0,0 +1,68 @@
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

@@ -0,0 +1,68 @@
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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
# 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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
# 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

@@ -29,7 +29,10 @@ gitea:
ALLOW_ONLY_EXTERNAL_REGISTRATION: true ALLOW_ONLY_EXTERNAL_REGISTRATION: true
ENABLE_BASIC_AUTHENTICATION: true ENABLE_BASIC_AUTHENTICATION: true
ENABLE_PASSWORD_SIGNIN_FORM: false ENABLE_PASSWORD_SIGNIN_FORM: false
ENABLE_NOTIFY_MAIL: true AUTO_WATCH_ON_CHANGES: false
AUTO_WATCH_NEW_REPOS: false
ENABLE_NOTIFY_MAIL: false
ENABLE_TIMETRACKING: false
openid: openid:
ENABLE_OPENID_SIGNIN: false ENABLE_OPENID_SIGNIN: false
@@ -127,7 +130,6 @@ persistence:
size: 10Gi size: 10Gi
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
storageClass: upcloud-block-storage-maxiops
# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes # -- Recreate strategy to avoid Multi-Attach errors with RWO volumes
strategy: strategy:
@@ -153,7 +155,6 @@ postgresql:
persistence: persistence:
enabled: true enabled: true
size: 8Gi size: 8Gi
storageClass: upcloud-block-storage-maxiops
resources: resources:
requests: requests:
cpu: 100m cpu: 100m

View File

@@ -10,18 +10,8 @@ opencost:
serviceName: prometheus-server serviceName: prometheus-server
namespaceName: monitoring namespaceName: monitoring
port: 80 port: 80
customPricing: # Cloud-specific pricing is in per-cluster value overrides
enabled: true # (e.g. infra/values/upc-dev/opencost-values.yaml)
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: ui:
enabled: false enabled: false
service: service:

View File

@@ -2,6 +2,8 @@ providers:
kubernetesIngress: kubernetesIngress:
publishedService: # Fixes ArgoCD health checks for LoadBalancer services publishedService: # Fixes ArgoCD health checks for LoadBalancer services
enabled: true enabled: true
kubernetesCRD:
allowCrossNamespace: true
deployment: deployment:
replicas: 2 replicas: 2
@@ -48,3 +50,26 @@ ports:
accessLogs: true accessLogs: true
metrics: true metrics: true
tracing: true tracing: true
gitea-ssh:
port: 2222
expose:
default: true
exposedPort: 2222
protocol: TCP
# -- IngressRouteTCP for Gitea SSH (cross-namespace to gitea/gitea-ssh service)
extraObjects:
- apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitea-ssh
spec:
entryPoints:
- gitea-ssh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
namespace: gitea
port: 22

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
# 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

@@ -0,0 +1,17 @@
# 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

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
# 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

@@ -0,0 +1,18 @@
# 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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
# 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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,12 @@
# 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

@@ -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

View File

@@ -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"

View File

@@ -10,6 +10,10 @@ service:
{ {
"name": "websecure", "name": "websecure",
"mode": "tcp" "mode": "tcp"
},
{
"name": "gitea-ssh",
"mode": "tcp"
} }
], ],
"backends": [ "backends": [
@@ -24,6 +28,9 @@ service:
"properties": { "properties": {
"outbound_proxy_protocol": "v2" "outbound_proxy_protocol": "v2"
} }
},
{
"name": "gitea-ssh"
} }
] ]
} }

View File

@@ -1,5 +1,5 @@
global: global:
domain: argocd.us.forteapps.net domain: argocd.fortedigital.com
notifications: notifications:
context: context:
clusterName: "dev-fd-us-east1" clusterName: "prod-fd-no-svg1"

View File

@@ -1,8 +1,8 @@
dot-ai: dot-ai:
ingress: ingress:
host: kubemcp.us.forteapps.net host: kubemcp.fortedigital.com
webUI: webUI:
baseUrl: http://kubemcpui.us.forteapps.net baseUrl: http://kubemcpui.fortedigital.com
dot-ai-ui: dot-ai-ui:
ingress: ingress:
host: kubemcpui.us.forteapps.net host: kubemcpui.fortedigital.com

View File

@@ -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

View File

@@ -1,3 +1,3 @@
ingress: ingress:
hosts: hosts:
- grafana.us.forteapps.net - grafana.fortedigital.com

View File

@@ -1,2 +1,2 @@
ingress: ingress:
hostname: id.us.forteapps.net hostname: id.fortedigital.com

View File

@@ -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"

View File

@@ -1,43 +0,0 @@
site_name: K8s Launchpad
site_description: Documentation for the GitOps-managed Kubernetes cluster
repo_url: https://git.forteapps.net/Forte/launchpad
repo_name: Forte/launchpad
theme:
name: material
palette:
- scheme: default
primary: indigo
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: indigo
toggle:
icon: material/brightness-4
name: Switch to light mode
features:
- navigation.instant
- navigation.sections
- navigation.top
- search.highlight
- content.code.copy
nav:
- Home: README.md
- GitOps Architecture: GITOPS-ARCHITECTURE.md
- Developer Guide: DEVELOPER-GUIDE.md
- Operations Runbook: OPERATIONS-RUNBOOK.md
- Technical Reference: REFERENCE.md
markdown_extensions:
- tables
- toc:
permalink: true
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- admonition
- pymdownx.details

23
scripts/backup/aws-s3.sh Normal file
View File

@@ -0,0 +1,23 @@
#!/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

@@ -0,0 +1,36 @@
#!/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."

26
scripts/backup/gcp-gcs.sh Normal file
View File

@@ -0,0 +1,26 @@
#!/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

@@ -0,0 +1,20 @@
#!/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."

100
scripts/gitea-backup-aks.sh Normal file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -euo pipefail
# Gitea backup helper for Azure Blob Storage
# Uses the gitea-backup-aks 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-aks.sh list # list all backups
# ./scripts/gitea-backup-aks.sh download <filename> # download a backup
# ./scripts/gitea-backup-aks.sh download latest # download the most recent backup
NAMESPACE="gitea"
SECRET="gitea-backup-aks"
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 <filename|latest>}"
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 <filename> Download a specific backup"
echo " $0 download latest Download the most recent backup"
;;
esac

View File

@@ -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.<region>.amazonaws.com
#
# Usage:
# ./scripts/gitea-backup-eks.sh list # list all backups
# ./scripts/gitea-backup-eks.sh download <filename> # download a backup to current dir
# ./scripts/gitea-backup-eks.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 <filename|latest>}"
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 <filename> Download a specific backup"
echo " $0 download latest Download the most recent backup"
;;
esac

View File

@@ -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-gke.sh list # list all backups
# ./scripts/gitea-backup-gke.sh download <filename> # download a backup
# ./scripts/gitea-backup-gke.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 <filename|latest>}"
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 <filename> Download a specific backup"
echo " $0 download latest Download the most recent backup"
;;
esac

View File

@@ -13,7 +13,7 @@ NAMESPACE="gitea"
SECRET="gitea-backup-s3" SECRET="gitea-backup-s3"
IMAGE="minio/mc:latest" IMAGE="minio/mc:latest"
POD_NAME="gitea-backup-helper" POD_NAME="gitea-backup-helper"
ALIAS_CMD='mc alias set upcloud ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null' ALIAS_CMD='mc alias set s3 ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
cleanup() { cleanup() {
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true 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 case "${1:-help}" in
list) list)
echo "Listing backups..." echo "Listing backups..."
mc_run 'mc ls upcloud/${S3_BUCKET}/' mc_run 'mc ls s3/${S3_BUCKET}/'
;; ;;
download) download)
@@ -49,7 +49,7 @@ case "${1:-help}" in
if [ "$FILE" = "latest" ]; then if [ "$FILE" = "latest" ]; then
echo "Finding latest backup..." echo "Finding latest backup..."
FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]') FILE=$(mc_run 'mc ls s3/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
if [ -z "$FILE" ]; then if [ -z "$FILE" ]; then
echo "No backups found." echo "No backups found."
exit 1 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 kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
echo "Saving to ./$FILE ..." echo "Saving to ./$FILE ..."
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE" kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat s3/\${S3_BUCKET}/$FILE" > "./$FILE"
cleanup cleanup
echo "Downloaded: ./$FILE" echo "Downloaded: ./$FILE"

1
shared-prompts Submodule

Submodule shared-prompts added at c5bc55b3d7