142 Commits

Author SHA1 Message Date
1808750c81 secrets 2026-05-20 18:35:23 +02:00
82a081d6a3 ppusher 2026-05-20 12:43:03 +02:00
c49d03d7f7 onlySSO 2026-05-16 23:04:11 +02:00
d47dba2ae5 signups 2026-05-16 22:12:04 +02:00
cf9eb47ecf script fix 2026-05-16 22:08:56 +02:00
3eca723f05 diffs 2026-05-16 22:05:02 +02:00
f36996da11 script fix 2026-05-16 21:57:44 +02:00
6bf7db21d0 registrar error 2026-05-16 21:55:44 +02:00
2641d55784 scopes 2026-05-16 21:53:36 +02:00
117297effc sso vw 2026-05-16 21:47:59 +02:00
fda90f9e01 adminToken enc 2026-05-16 21:34:34 +02:00
1124377d97 adminToken 2026-05-16 21:29:14 +02:00
c0710b89bb no signup 2026-05-16 21:15:38 +02:00
d7bda18aea domain 2026-05-16 21:11:17 +02:00
2796e1b9d3 name 2026-05-16 21:09:04 +02:00
d7a0c26117 icon 2026-05-16 21:08:36 +02:00
693f2f9168 homepage 2026-05-16 21:07:29 +02:00
2509ef062c domain restriction 2026-05-16 20:58:00 +02:00
957757e557 host 2026-05-16 20:51:44 +02:00
070799da05 bitw 2026-05-16 20:49:25 +02:00
1a2817e537 domain fix 2026-05-16 20:42:17 +02:00
b47b0035f5 smtp auth 2026-05-16 20:38:21 +02:00
d3fac4d43e smtp port 2026-05-16 20:34:22 +02:00
c37bd3ef04 from 2026-05-16 20:30:32 +02:00
ad661ba3dd allow signup 2026-05-16 20:27:36 +02:00
a9625f96e6 db secrets 2026-05-16 20:23:58 +02:00
cb64edc927 cleanup 2026-05-16 20:18:48 +02:00
ac1c242fb9 kust 2026-05-16 20:17:14 +02:00
4b29c07fd6 secret 2026-05-16 20:15:37 +02:00
52732626e5 ignorediffs 2026-05-16 20:10:19 +02:00
8634436dd4 StatefulSet 2026-05-16 20:07:17 +02:00
a8baa169e9 secrets vw 2026-05-16 20:00:22 +02:00
73ef3a6e12 pg fix 2026-05-16 19:49:38 +02:00
302705d374 icon 2026-05-16 19:45:19 +02:00
f3286ef77e homepage vw 2026-05-16 19:44:17 +02:00
74f4f86770 vw apps 2026-05-16 19:34:42 +02:00
f2c56156bf vw postgres 2026-05-16 18:10:14 +02:00
21fb50ba00 vw fixes 2026-05-16 15:55:18 +02:00
b90b630b06 comment 2026-05-16 15:52:10 +02:00
66de9b8a0a replicas 2026-05-16 15:48:13 +02:00
716c552be9 ns 2026-05-16 15:44:04 +02:00
f048b47a0f vaultwarden 2026-05-16 15:39:55 +02:00
66f40427ee mappings 2026-05-15 15:47:25 +02:00
332881cbd0 fix 2026-05-14 23:47:14 +02:00
f363afa087 browser flow override 2026-05-14 23:43:40 +02:00
bc42347cb6 gitea+ACCOUNT_LINKING 2026-05-14 21:30:53 +02:00
80d7bff4bc groups 2026-05-14 21:18:17 +02:00
3644a3ec87 mappers 2026-05-14 21:14:57 +02:00
bd478478f1 fix attemt 2026-05-14 20:40:44 +02:00
67b1d95509 account linking 2026-05-14 19:39:38 +02:00
fff95d98a5 remove protocol mappers 2026-05-13 23:15:28 +02:00
8b743efa43 KC fix 2026-05-13 23:13:09 +02:00
4ca9039686 kpolicy 2026-04-29 12:54:07 +02:00
6a9eadbde8 vault ignore diffs 2026-04-29 12:50:10 +02:00
f19f7c9237 icon 2026-04-29 12:07:04 +02:00
5a459d486e dbunk-demo 2026-04-29 10:53:35 +02:00
31fb476a78 row 2026-04-29 10:06:02 +02:00
a088425b70 homepage config 2026-04-29 10:04:20 +02:00
b3b3edf82c no header 2026-04-28 23:03:15 +02:00
308755a4b3 layout 2026-04-28 23:02:13 +02:00
db6afaf180 vault
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 22:44:57 +02:00
5a2f9a1b88 Update infra/values/base/keycloak-values.yaml
Signed-off-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-28 19:27:38 +00:00
1c6f18b67c homepage 2026-04-28 20:38:59 +02:00
7132f5000e docs 2026-04-27 20:35:27 +02:00
b4100bd456 mm ns 2026-04-27 20:16:06 +02:00
fff117a500 ns 2026-04-27 17:40:46 +02:00
03c75fc4cd mm ns 2026-04-27 17:40:05 +02:00
df73c4bdc0 mm sync pol 2026-04-27 17:37:54 +02:00
6a7de704f2 enterprise-apps 2026-04-27 17:34:43 +02:00
be8bbd2c12 aksapps 2026-04-27 17:33:47 +02:00
c469ab44b0 ent apps 2026-04-27 17:28:48 +02:00
290c8b91f8 db pass 2026-04-27 14:05:38 +02:00
a776bae4bd image tag 2026-04-27 13:00:37 +02:00
7405ce27dd chart name 2026-04-27 12:55:20 +02:00
1281e8ef37 databunker 2026-04-27 12:54:18 +02:00
c497c54e8e fix 2026-04-27 12:28:47 +02:00
b57459cf85 rm secrets2 2026-04-27 12:25:25 +02:00
e8dd213685 rm secrets 2026-04-27 12:24:14 +02:00
1d879c82f9 secrets shuffle 2026-04-27 12:21:50 +02:00
94c8265475 overlays2 2026-04-27 12:01:59 +02:00
17d7c4a655 overlays 2026-04-27 11:49:10 +02:00
f3dba72c5d aks-dev 2026-04-27 11:33:24 +02:00
cc9c9049eb ignore diff 2026-04-26 23:55:55 +02:00
9f6c5105af netpol all remove 2026-04-25 16:04:13 +02:00
45e502d74d argocd tls 2026-04-25 11:49:17 +02:00
167d893233 clean scopes gitea 2026-04-24 20:18:02 +02:00
8b9ffee242 socpes 2026-04-24 20:14:28 +02:00
4069e255a8 org scope 2026-04-24 20:05:01 +02:00
3b1f498616 Update infra/values/base/keycloak-values.yaml 2026-04-24 17:40:15 +00:00
cc47bf6b9f grafana access 2026-04-24 15:49:47 +02:00
c1d61398f0 SSO grafana 2026-04-24 15:45:50 +02:00
ece4a8d199 grafana tls 2026-04-24 15:39:46 +02:00
03c47ad109 remove trivy 2026-04-24 15:24:58 +02:00
3095741590 clear KC scopes 2026-04-24 15:13:58 +02:00
d7ba859e61 no openid 2026-04-24 15:09:10 +02:00
07eb9b7051 optional scopes 2026-04-24 15:05:07 +02:00
a911ff64c3 kc scopes 2026-04-24 15:03:14 +02:00
9e13560e5e basic scope 2026-04-24 14:40:32 +02:00
3d84acb278 DEFAULT_EMAIL_NOTIFICATIONS 2026-04-24 14:25:29 +02:00
fde81c6ec6 dbox 2026-04-24 13:42:52 +02:00
8648269e55 Update secrets/base/kustomization.yaml 2026-04-24 11:25:38 +00:00
84fe4cbe7c ts-mcp-secrets-sealed.yaml 2026-04-24 13:15:00 +02:00
38158be0a8 doc 2026-04-24 12:55:50 +02:00
202e84badc doc 2026-04-24 12:54:26 +02:00
a6df75de93 dbox 2026-04-24 12:38:50 +02:00
4f4f544100 k 2026-04-24 10:58:20 +02:00
8d4b6493a0 mm 2026-04-24 10:57:53 +02:00
8505481291 feature/multi-cloud (#14)
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #14
2026-04-24 08:48:53 +00:00
65598c9297 karpor diffs 2026-04-24 09:47:52 +02:00
3f0f70699b karpor 2026-04-24 09:43:16 +02:00
06522b2f19 ts-mcp 2026-04-23 14:44:33 +02:00
4c65035485 ns 2026-04-23 14:11:45 +02:00
84f4bebc08 ts-mcp 2026-04-23 13:41:51 +02:00
5394b2c714 ts-mcp 2026-04-23 13:40:33 +02:00
c4e586a7be ts-mcp 2026-04-23 13:38:47 +02:00
1fa070b041 argo 2026-04-23 13:35:42 +02:00
9c905355e3 argocd known host 2026-04-23 13:28:34 +02:00
6b1115ec28 argocd disable submodule 2026-04-23 13:09:02 +02:00
2fb276a62c ts-mcp 2026-04-23 13:02:00 +02:00
3efe1b68ef auth doc 2026-04-23 10:05:15 +02:00
5df104beec sp 2026-04-22 13:54:51 +02:00
0ecfee3cf8 prompts 2026-04-22 13:51:38 +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
72a65f0e06 client cloner (#3)
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
Reviewed-on: #3
Reviewed-by: gitea_admin <admin@forteapps.net>
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-17 13:42:44 +00:00
223 changed files with 5057 additions and 3011 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Force LF line endings for shell scripts
*.sh text eol=lf

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"

101
README.md
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)]()
--- ---
@@ -57,7 +57,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
### What's Inside ### What's Inside
- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets - **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets, Homepage (platform dashboard)
- **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP - **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP
- **Policies**: Kyverno security policies for secret management, namespace controls, pod verification - **Policies**: Kyverno security policies for secret management, namespace controls, pod verification
- **Monitoring**: Full observability stack with metrics, logs, traces, and alerting - **Monitoring**: Full observability stack with metrics, logs, traces, and alerting
@@ -83,26 +83,52 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern) ├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern)
├── infra/ # Infrastructure ArgoCD Applications ├── infra/ # Infrastructure ArgoCD Applications (Kustomize multi-cluster)
│ ├── enterprise-apps.yaml # Manages all apps in apps/ folder │ ├── base/ # Base ArgoCD Application manifests (one dir per component)
│ ├── traefik-application.yaml │ ├── kustomization.yaml # Aggregates all component subdirectories
│ ├── cert-manager-application.yaml │ ├── traefik-application/
│ ├── kyverno.yaml │ │ │ ├── kustomization.yaml
├── prometheus.yaml │ │ └── traefik-application.yaml
│ ├── grafana.yaml │ ├── keycloak/
├── loki.yaml │ │ ├── kustomization.yaml
├── tempo.yaml │ │ └── keycloak.yaml
│ ├── fluent-bit.yaml │ ├── grafana/
│ ├── trivy.yaml │ ├── prometheus/
│ ├── sealedsecrets.yaml │ ├── ... # Each component in its own subdirectory
├── renovate.yaml │ └── secrets/
│ ├── overlays/ # Per-cluster overrides (Kustomize)
│ │ ├── upc-dev/ # UpCloud Dev — includes all base components
│ │ ├── upc-prod/ # UpCloud Prod — all components + patches
│ │ ├── aks-dev/ # Azure AKS Dev — selective components only
│ │ ├── aks-prod/ # Azure AKS Prod
│ │ ├── eks-dev/ # AWS EKS Dev
│ │ ├── eks-prod/ # AWS EKS Prod
│ │ ├── gke-dev/ # GCP GKE Dev
│ │ └── gke-prod/ # GCP GKE Prod
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides │ └── values/ # Helm value overrides
│ ├── base/ # Shared cloud-agnostic values
│ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing)
│ ├── 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 (Kustomize, same pattern as infra)
│ ├── mcp10x.yaml │ ├── base/ # One subdirectory per app
│ ├── musicman.yaml │ ├── kustomization.yaml
│ ├── dot-ai-stack.yaml │ ├── musicman/
└── argo-mcp.yaml │ ├── mcp10x/
│ │ ├── dot-ai-stack/
│ │ ├── ts-mcp/
│ │ └── argo-mcp/
│ └── overlays/ # Per-cluster: cherry-pick or include all
│ ├── upc-dev/ # All apps
│ ├── upc-prod/ # All apps + patches
│ └── aks-dev/ # Selective apps only
├── cluster-resources/ # Cluster-wide Kubernetes resources ├── cluster-resources/ # Cluster-wide Kubernetes resources
│ ├── letsencrypt-issuer.yaml │ ├── letsencrypt-issuer.yaml
@@ -140,12 +166,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)
@@ -160,7 +186,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!
@@ -169,8 +195,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
@@ -198,7 +224,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:
@@ -337,7 +363,6 @@ kubectl patch application myapp -n argocd \
| **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet |
| **OpenCost** | Cost monitoring | `monitoring` | 1 | | **OpenCost** | Cost monitoring | `monitoring` | 1 |
| **Renovate** | Dependency updates | `renovate` | CronJob | | **Renovate** | Dependency updates | `renovate` | CronJob |
| **Trivy** | Vulnerability scanning | `trivy-system` | 1 |
**Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components) **Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components)
@@ -355,12 +380,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/`. Each YAML in `infra/` becomes a child Application managed by ArgoCD. `_app-of-apps-{cluster}.yaml` is the root Application that manages all other Applications in `infra/`. Each component in `infra/base/` lives in its own subdirectory (e.g., `infra/base/grafana/`). Overlays can either include **all** components (via `../../base`) or **cherry-pick** specific ones (via `../../base/grafana`, `../../base/prometheus`, etc.). Per-cluster patches swap Helm 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.
@@ -429,7 +454,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!
@@ -452,16 +477,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)
- **Cluster**: Single cluster
- **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
- Single cluster (no multi-cluster setup)
- 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)
@@ -479,8 +502,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
--- ---
@@ -498,7 +521,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)!**

View File

@@ -20,7 +20,7 @@ spec:
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: infra path: infra/overlays/aks-dev
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: default namespace: default

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

View File

@@ -1,33 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
name: network-policies name: infrastructure-apps
namespace: argocd namespace: argocd
labels: labels:
app.kubernetes.io/name: network-policies app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
finalizers: finalizers:
- resources-finalizer.argocd.argoproj.io - resources-finalizer.argocd.argoproj.io
spec: spec:
project: default project: default
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: cluster-resources/network path: infra/overlays/eks-dev
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: default
syncPolicy: syncPolicy:
automated: automated:
prune: true prune: true
selfHeal: true selfHeal: true
allowEmpty: false
syncOptions: syncOptions:
- Validate=true - CreateNamespace=true
- ServerSideApply=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

32
_app-of-apps-upc-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/upc-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/upc-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- argo-mcp.yaml
- argocdmcp-auth-oidc-sealed.yaml
- argocd-mcp-credentials.yaml

View File

@@ -0,0 +1,18 @@
# SealedSecret created after namespace (sync-wave: 0)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai
spec:
encryptedData:
anthropic-api-key: AgASIRVNs7kIvZ/XwFJ4j9TJ05TW4YlFNzi2lx+6URlvzjTkMignK+y4/HD3wTK37BkIonnOXf/DGJcMgqgxOrHBVu9tLkSamve5qgf+SOZ8jxaqpLX2e5hdmHrgMp7rVWKiOoxM+bvu8Gdve3eMwXX8Eazj7W7vi430LsvW4BmPrQp+A9SOP+hwMlV5r+P5pUogC3hddt5KoSwpgnpOb8fRGlLDpc548eDNBJrhsAZiiFqc8+CycHX6idxWzlWOTPR05LBs7YrTIP/LUXWdyq6UG8yB5D0c9JNndnGo6kSfJxdaxCNO7GhMAOmo4CitzYn/zhICwuHPLKwUYQBcEHVpFxgub0U9fY7s4i0SAH9aF1kk8yqHWaCicHzi6B3AxMZYr+knS5vVNB+uGZmSKgZ7QNE1bF6E4Gg5bXRwk7OVQhuJSBzxVNDoXoJ6stL8ejU+MP5FtI792FxZbNDImu3kqor/t9wB49ZeB9Ngks2yvtlSrQ3G2lxJaOgNBPmKYhAX95JFI3xUSHOMa18ftn+aXmEQTXOHtM/IT8A95gUvIKz8Hrt03CwgZ8E+3lcsjAVsfNjqn/erqV+xi93OYOL6o9ivmTIem3MzsqjKuQ57HKaghnd+Ygdt/WfotRBSgZtCweOkZBV0XKxI1RmCQXk6dce8x4T7FDEeaSQwkc4yZ77LfQJnyVgU1rds9Hqv0RbymHK8JCQEMMb6PcK+Ow068fdj4NP27HkuYMB5JdPp682KIRwGznN2d3+QPXRd/ZHshnixudY7CLirlZl9xD0HssfSBI+uCrFEAln8D93UYbn7trF5gkKWYQsQ07CVZ5Z1qXieqwJWjHRY+oY=
auth-token: AgB65rUY3yJuTTLbGyrCtJPZp8UyEOr+kznSMGvUaZ9PoHq+kIEhazRdVKHa/WGvMWuc4x+XYRuIGj+2KiooyilUPrLcE3bz9fUOAFbRw3+iUh3WAgwK+f3eBUG6/0OoFw+GJ583IRidDW1t2BchMxSM1m+3vmQoF24qj7k9j/lWPsnX2IX3DhM0SomD1xmG+LsQMo2e4vQXB0BxhVDIQ621JTrvUPYzYx0NlPqZO/MtR1JWYS7WBTvegvgeBKLxfy9KLnqsuzu7rc2t9BYt7TRqvBg/prrKPdSV6Ei4GOZq+AcG15iOKXhsj8SxejPpM0QDemFTRQkdfz+k8ms4/SM0eylr5fEaa99TMTvjYGCfjJJyRcVm5Ef/XdmXiM3OI1u+9QNPiqXh1zmtp0yQMZ7nRE70kKQ2MHVhEmSUkBjovybIzLk4OL1v0FGDDqa1BN9KmNEBlo8H7DqXzfamVoNPyuuO625B3HgSeR3Udq8hngy9W/wiolmMNSc9C6vBA0HzE7Mkq79fHh/ruTi5zOJLfyEP7KQQqlNKfEtDFQfabaV9ERthQjuUs8ZIrdfCTOyQkrmmGY/sH5yodLRSYSEpHFrhPepwHS7lGzbVucW3Fn34D4OxJXyXQQObZKybLV7g6Oglf2Pdn11CEu/4+19RlykuOlm1dlj852a5NWWmmX319laLGlEd1BpG4cZyc9UZud68dGumwzrUupUMTLlJx8bR5rLgqaMpQMluwvq8MQbBXm4ySOcQ2jQhjw==
openai-api-key: AgBc4cSrd0riSxKPrp7AqrNYMDAZJTe3aCjTOwEb5pS/KARkgoEGz+0ZhLXa2bcCsPlQzqRzZ1c1cUCnBACkp1xoMfhSOoIJUWJQHRgZx5kEDhNIE/M8PQe8es7jYsW7/ui6iK8uj3Ljk21r8l/ctnP/KiKyML4553el28Ya7XsPQynRvpVexFC4BoJw50nkauLnvl3in0cKmEVAkMWorUP3Etapaj4DWkCQQ22RPN0xGNo9yiIUEAVE+uTRFNKGEHMIvJzgB291FJQtG/WoN/Sy/DeoZkUYndA7CbfFgiz9sq3GqJwyqTM+Ikzw6VCQ3TVWYO/6+yaEzT1NZ/rw00YhDyFoH+yGTkX5lrSUo8lGf3T9OODTdSS1203xtj4dhGbY70sPGMxDfucnO6piVcuRfh9bWg1VX+MTjqpBQE1J0DKJanhoh6lKbPOJhRbi0TIoCz1a3btbbKLDq2YJl7FXA3QNdBsR4qVJ3xmgWlH/eTUskCE2YxDzHmkxgZN2mL22SfeUJYtDRswRG0UGc6pvg2xrR0iEDB0UbH4FQK46fWv8aiOejteLlAAyA9HNA8yi73rTEjq55wpO19/MYj+6oUGEHpFaJje77INTHAKpdbfkp8iKotNXFYLM2hsjyau+x/AFjg87kEdIUVVHHLVMDhOq6NO+foM0nFOIv4lr2Yjl15+ImMSTEfcBr+nWaomnb9G8i5ptqz9bMHbxAcHpzWUBH9SZhzrCWZNDzOm2X2K6mpXhg4WX7VJMoIJk/f+bxTTKnWBawdnpCdbdG5GYQh7usqjPuELFYRTsx+6Tqoddp4KIEyMMxO81XObiN5vEBg74ygunxrjKNg5FoOkUA79YvcGBVVU1FNRl3d8IslXFqhEOT35nxGBB0T7ZORii34tZ2E551NkIo1WbCwilD3Dlgw==
ui-auth-token: AgDStP4jHhMehx/SAv5x2aQrJwChnWq8WhV/LVTSuuCSSy9kFQGa3Izyl21sMhLKUgrlS030GH50lbku+IY90S+MSBfaCq0Xb8qwohfH+qJulMRgljoU8r58/3ZsaBzbGJjiNMgBCwGlvuK85t3R3662ftwikahWqLfwAahi12L0llUDNyG9UB0Os3oR2CVAt83+YB07YspCWRzwuOz9D1SgOL/g/JF2hoL43pDD65BqYKdEuLFInJZx1Ul68V/FPmJK1gmJb/Kv8pgtk0sGTzxbbTdIQ1Ugf6+8//CWEq6GsMeEZG9VwExL/KYqobnbqd02FRgSSvWybhWLjiALLQvAFtce6FR6bur0rsariyogGjYCuXYdRnhf7QnB6NopwplhFP37Qf6E4q2pJTJ6Fzv0xq+XlfzNkn9Jvebrdkk/5CThW+wPAfx8r1VIySqBsYnPO/ZQDkfQ5ocX6fmzZ1iCg+0NI24k2YwCeMZtEAGpKEtejS0BLvqrilUG6Oz9sHCGHro4Bv5B/jzUjGbye0DSDj1f6c2RcpqMiUxfPvydJIcJGhrTx8sZS3qhEWME7kwjJCZpHEzfdv0weSJbgVGBSh9e1wjZxxeJGXuZKdFzdhpNEWP4uScGW0UnRDwxZzHMsLjS/W30mQmwmgJVTKdPl6VQrvdq7m4AC6+/d7iXlHazieC1KpBme4hWzO+/h7qRw1P5Va/ZWZtnGs8476J8hIojRtOJ/CdWwVLUa0gHo4CYnempIMwCIS3GtA==
template:
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai

View File

@@ -27,29 +27,19 @@ metadata:
spec: spec:
project: default project: default
source: sources:
repoURL: ghcr.io/vfarcic/dot-ai-stack/charts - repoURL: ghcr.io/vfarcic/dot-ai-stack/charts
chart: dot-ai-stack chart: dot-ai-stack
targetRevision: "0.56.0" targetRevision: "0.56.0"
helm: helm:
releaseName: dot-ai-stack releaseName: dot-ai-stack
values: | valueFiles:
dot-ai: - $values/infra/values/base/dot-ai-stack-values.yaml
ingress: - $values/infra/values/upc-dev/dot-ai-stack-values.yaml
enabled: true
className: traefik - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
host: kubemcp.forteapps.net targetRevision: HEAD
webUI: ref: values
baseUrl: http://kubemcpui.forteapps.net
dot-ai-ui:
uiAuth:
secretRef:
name: dot-ai-secrets
ingress:
enabled: true
className: traefik
host: kubemcpui.forteapps.net
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dot-ai-stack.yaml
- dot-ai-secrets.yaml

View File

@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dot-ai-stack
- mcp10x
- musicman
- ts-mcp
- argo-mcp

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- mcp10x.yaml
- forte10x-app-credentials-sealed.yaml

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- musicman.yaml
- musicman-credentials.yaml

View File

@@ -4,6 +4,8 @@ metadata:
creationTimestamp: null creationTimestamp: null
name: musicman-credentials name: musicman-credentials
namespace: music-man namespace: music-man
annotations:
argocd.argoproj.io/sync-wave: "12"
spec: spec:
encryptedData: encryptedData:
DATABASE_URL: AgBGLu8Rw9z9WMo3uX7fezN7tOVlEsmWtikFlyBxuSuQ1dCv6KTCePkwxJx4LuKvaHXlwdWl5yP8wQxMJP0BNJ1wewFb9zeUkP1YuCz4MrfuXq1zrecIr86R5hNbPiOb66e/4oOTCY/z3QREX9WjZdLJV/PCyBz8MP0D51pgWXpM6CBdhwpFbHSALyJk89+q44c9KkRxAUG2OLnesMeRe9nXJt5ariUCl9Qd2POIjx2hSNII1l0KbTcjI9hCf91DYM6poqKYYQUpnrjKv3LJwWS79I2b56+iTtroH3usIRgaiwgtFt2INm+8gwLBmC4xxKJ5VAjjYB/3dcN9XeboXvj0NB05P9jS3e77imUFANIB9coeaNlcvRWxwGCewYMp8+7RT7jPVA41/+aT/zT74tq9WhkKvgrr1It9/5fRnXtFEkhZg5bBcYCChzooarHkiwKlA3Wo0CrFsDPqy89oZrnwMRnVqKWBf79koZV4l7uCA0do9ojf55lTy8mt3mKQkwfqK9UdzZNbYzH0/Fk6gxlSxANOOqe7kt6VPywYUBnh6JS5U+kdTgNeSrFy/xqLFz28fXuikSJvLEouSFu66MeT+6uvYEmdfdLeh7quW/n+p7QTok3v3kRYJ/1Dl8ZtgvM7e8F/J5bLcacj394AJ/bBt+RIDa+XBjNNPrWKcWt/mkudZ25F/84G+hNxYQv7PIbhYfA1JTuHmQSoF+xah5QhKpyNpI3+knJmJj/4MhPKLnTuebg0xfbPevm2CU9fSa4sPIqmSvSGtqlXODvCfDSFEYzWfyfXV5Tys1NGAt04V8fl9A9UxULUm510NCeD0jzFeeYm3ZJiyavA5xF6hXCHoqLE DATABASE_URL: AgBGLu8Rw9z9WMo3uX7fezN7tOVlEsmWtikFlyBxuSuQ1dCv6KTCePkwxJx4LuKvaHXlwdWl5yP8wQxMJP0BNJ1wewFb9zeUkP1YuCz4MrfuXq1zrecIr86R5hNbPiOb66e/4oOTCY/z3QREX9WjZdLJV/PCyBz8MP0D51pgWXpM6CBdhwpFbHSALyJk89+q44c9KkRxAUG2OLnesMeRe9nXJt5ariUCl9Qd2POIjx2hSNII1l0KbTcjI9hCf91DYM6poqKYYQUpnrjKv3LJwWS79I2b56+iTtroH3usIRgaiwgtFt2INm+8gwLBmC4xxKJ5VAjjYB/3dcN9XeboXvj0NB05P9jS3e77imUFANIB9coeaNlcvRWxwGCewYMp8+7RT7jPVA41/+aT/zT74tq9WhkKvgrr1It9/5fRnXtFEkhZg5bBcYCChzooarHkiwKlA3Wo0CrFsDPqy89oZrnwMRnVqKWBf79koZV4l7uCA0do9ojf55lTy8mt3mKQkwfqK9UdzZNbYzH0/Fk6gxlSxANOOqe7kt6VPywYUBnh6JS5U+kdTgNeSrFy/xqLFz28fXuikSJvLEouSFu66MeT+6uvYEmdfdLeh7quW/n+p7QTok3v3kRYJ/1Dl8ZtgvM7e8F/J5bLcacj394AJ/bBt+RIDa+XBjNNPrWKcWt/mkudZ25F/84G+hNxYQv7PIbhYfA1JTuHmQSoF+xah5QhKpyNpI3+knJmJj/4MhPKLnTuebg0xfbPevm2CU9fSa4sPIqmSvSGtqlXODvCfDSFEYzWfyfXV5Tys1NGAt04V8fl9A9UxULUm510NCeD0jzFeeYm3ZJiyavA5xF6hXCHoqLE

View File

@@ -36,13 +36,8 @@ spec:
automated: automated:
prune: true prune: true
selfHeal: true selfHeal: true
allowEmpty: false
syncOptions: syncOptions:
- CreateNamespace=true - CreateNamespace=true
- Validate=true
- ServerSideApply=false
- Replace=false
retry: retry:
limit: 5 limit: 5
backoff: backoff:

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ts-mcp.yaml
- ts-mcp-secrets-sealed.yaml

View File

@@ -0,0 +1,13 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: ts-mcp-secrets
namespace: ts-mcp
spec:
encryptedData:
AZURE_CLIENT_SECRET: AgCWj525+NHkZ8XG97hEe4RS0SDC0QIGDXmEvzSlIqJQ9XVZEeKxVuAYmJ+w/HH7zBXD3qlZISeOPKn3FbMEeRukmYK0d5PsH26tRUMPoMzwWCuQkZIQ83uX9Pz/wMiqW8aZFIxpdEiUgVdanxHSFoDRPC1VlSEtV9B9yN2MgXBID5s0oje5BM9ttc4WVRe6+9pMeaOC6u+YUgcfY7xPLetZfC9nQO4zn4jYhoQXfAddwMzNODvQNGPzIv6PXDXJweTwdmtGaxM6eDdcCJI/30bEV9prA5m6UlgTZ/Qp+onU70KdkBA9gM9tMMVUR6j/2sbWzqMP/rVaFLeUH1PjHv15n4EieWyuDyYEfmZNDFXc7O9RIK6P0jCIE+t3myxK2ZQ7cfXprdOSj94au0qP6leat0UUVoc9CFJHHtrNxXYWl7IYVhwvIQCMSgO2qoAXkdW4wKVJAcbJadJjoL2pWxzjaD4GgnUaAxWBANqZI2lD8CED4VfUVMB0ZUYRS/zvy/eqIGlT8WbzwTYFi3YDZRvAUIknxaWEavIG4x52d0FqTmFYY06W53fGYfBrUjJI54GWYyBpKdZTf7b/AlAN0+kwkk6OqsUWwWDqxR7LVCcPhjSIKd/THp+Tbq9z5TiPIHxOO9V60u51f8IoQrEgQfNov7CEGQZ8B9HUGObjNc5MhujzBJasMhrUcd2Ddk6KWk07B7223p/gIEM+81ZWQYUcc29+U/j1dQyRNZy/TC56ywe5DDBJSoGp
template:
metadata:
name: ts-mcp-secrets
namespace: ts-mcp

View File

@@ -1,27 +1,37 @@
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
name: secrets name: ts-mcp
namespace: argocd namespace: argocd
annotations: annotations:
argocd.argoproj.io/sync-wave: "2" argocd.argoproj.io/sync-wave: "11"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: "" notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: "" notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: "" notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels: labels:
app.kubernetes.io/name: secrets app.kubernetes.io/name: ts-mcp
app.kubernetes.io/part-of: platform app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd app.kubernetes.io/managed-by: argocd
finalizers: finalizers:
- resources-finalizer.argocd.argoproj.io - resources-finalizer.argocd.argoproj.io
spec: spec:
project: default project: default
source: sources:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: secrets path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/ts-mcp/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: secrets namespace: ts-mcp
syncPolicy: syncPolicy:
automated: automated:
prune: true prune: true

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base/musicman

View File

@@ -0,0 +1,47 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dbunk-demo
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "12"
labels:
app.kubernetes.io/name: dbunk-demo
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/dbunk-demo/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: dbunk-demo
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dbunk-demo.yaml

View File

@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- dbunk-demo
# No patches needed — base already has "upc-dev" paths
# upc-dev is the default/base cluster

View File

@@ -0,0 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
# dot-ai-stack: swap upc-dev → upc-prod
- target:
kind: Application
name: dot-ai-stack
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/dot-ai-stack-values.yaml

View File

@@ -1,8 +1,16 @@
#!/bin/zsh #!/bin/zsh
# 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
echo "running $0..." 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}..."
# Source cluster config
eval $(yq -r 'to_entries[] | "export \(.key)=\"\(.value)\""' "clusters/${CLUSTER}.yaml")
echo "Bootstrapping cluster: ${clusterName} (${CLUSTER})..."
############################################################ ############################################################
# Bootstrap # # Bootstrap #
@@ -10,18 +18,18 @@ echo "running $0..."
Bootstrap() Bootstrap()
{ {
ArgoCd ArgoCd
# Github Gitea
} }
############################################################ ############################################################
# Github # # Gitea #
############################################################ ############################################################
Github() Gitea()
{ {
echo "Installing secret..." echo "Installing secret..."
kubectl apply -f private/github.yaml kubectl apply -f "private/${CLUSTER}/gitea-repo-main.yaml"
kubectl apply -f private/main.key kubectl apply -f "private/${CLUSTER}/main.key"
} }
############################################################ ############################################################
@@ -29,17 +37,22 @@ Github()
############################################################ ############################################################
ArgoCd() ArgoCd()
{ {
# Pre-create ConfigMap for repo-server env (must exist before Helm upgrade)
kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f cluster-resources/argocd-repo-server-config.yaml
# install argocd # install argocd
echo "Installing ArgoCD..." echo "Installing ArgoCD..."
CLUSTER_NAME="${CLUSTER_NAME:-dev-fd-no-svg1}"
helm upgrade --install argocd argo-cd \ helm upgrade --install argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--version "7.8.0" \
--namespace argocd --create-namespace \ --namespace argocd --create-namespace \
--values infra/values/argocd-values.yaml \ --values infra/values/base/argocd-values.yaml \
--set notifications.context.clusterName="$CLUSTER_NAME" \ --values "infra/values/${CLUSTER}/argocd-values.yaml" \
--set notifications.context.clusterName="${clusterName}" \
--timeout 60s --atomic --timeout 60s --atomic
kubectl apply -f _app-of-apps.yaml -n argocd kubectl apply -f "_app-of-apps-${CLUSTER}.yaml" -n argocd
} }
Bootstrap Bootstrap

View File

@@ -0,0 +1,83 @@
# CronJob: syncs OIDC client secret from registrar-managed
# argocd-oidc-credentials into argocd-secret (oidc.clientSecret key).
# Runs every 2 min. No-ops if source secret doesn't exist yet
# (safe for fresh deploys before Keycloak is up).
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-oidc-sync
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: argocd-oidc-sync
namespace: argocd
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["argocd-oidc-credentials", "argocd-secret"]
verbs: ["get", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: argocd-oidc-sync
namespace: argocd
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: argocd-oidc-sync
subjects:
- kind: ServiceAccount
name: argocd-oidc-sync
namespace: argocd
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: argocd-oidc-sync
namespace: argocd
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
serviceAccountName: argocd-oidc-sync
restartPolicy: Never
containers:
- name: sync
image: bitnami/kubectl:latest
command: ["/bin/sh", "-c"]
args:
- |
set -e
# Exit gracefully if source secret doesn't exist yet
if ! kubectl get secret argocd-oidc-credentials -n argocd >/dev/null 2>&1; then
echo "argocd-oidc-credentials not found — skipping (Keycloak not ready yet)"
exit 0
fi
# Read current OIDC client secret
NEW_SECRET=$(kubectl get secret argocd-oidc-credentials -n argocd \
-o jsonpath='{.data.client-secret}' | base64 -d)
# Read current value in argocd-secret (if any)
CURRENT=$(kubectl get secret argocd-secret -n argocd \
-o jsonpath='{.data.oidc\.clientSecret}' 2>/dev/null | base64 -d || echo "")
# Only patch if changed
if [ "$NEW_SECRET" = "$CURRENT" ]; then
echo "oidc.clientSecret already up to date"
exit 0
fi
kubectl patch secret argocd-secret -n argocd --type merge \
-p "{\"stringData\":{\"oidc.clientSecret\":\"${NEW_SECRET}\"}}"
echo "Patched argocd-secret with oidc.clientSecret"

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-repo-server-config
namespace: argocd
data:
# Disable git submodule checkout - submodules (e.g. shared-prompts)
# are not needed for K8s manifest generation
ARGOCD_GIT_MODULES_ENABLED: "false"

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:

View File

@@ -243,8 +243,8 @@ spec:
- name: AUTH_OIDC_CLIENT_SECRET - name: AUTH_OIDC_CLIENT_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: auth-oidc name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}"
key: client-secret key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}"
resources: resources:
limits: limits:
cpu: 50m cpu: 50m
@@ -410,8 +410,8 @@ spec:
- name: AUTH_OAUTH_CLIENT_SECRET - name: AUTH_OAUTH_CLIENT_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: auth-oauth name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret\" || 'auth-oauth' }}"
key: client-secret key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret-key\" || 'client-secret' }}"
- name: AUTH_OAUTH_DELEGATION_CLIENT_SECRET - name: AUTH_OAUTH_DELEGATION_CLIENT_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:

View File

@@ -0,0 +1,37 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keycloak-client-config-cloner
spec:
rules:
- name: clone-client-config-to-keycloak
skipBackgroundRequests: false
match:
any:
- resources:
kinds:
- Secret
selector:
matchLabels:
keycloak.forteapps.net/client-config: "true"
exclude:
any:
- resources:
namespaces:
- keycloak
generate:
apiVersion: v1
kind: Secret
name: "{{request.object.metadata.name}}"
namespace: keycloak
synchronize: true
data:
metadata:
labels:
keycloak.forteapps.net/client-config: "true"
keycloak.forteapps.net/source-namespace: "{{request.object.metadata.namespace}}"
annotations:
keycloak.forteapps.net/source-name: "{{request.object.metadata.name}}"
keycloak.forteapps.net/source-namespace: "{{request.object.metadata.namespace}}"
data: "{{request.object.data}}"
type: "{{request.object.type}}"

View File

@@ -26,7 +26,6 @@ spec:
- monitoring - monitoring
- secrets - secrets
- kyverno - kyverno
- trivy-system
match: match:
any: any:
- resources: - resources:

View File

@@ -16,7 +16,6 @@ spec:
- resources: - resources:
namespaces: namespaces:
- kube-system - kube-system
- trivy-system
- monitoring - monitoring
- argocd - argocd
- cert-manager - cert-manager

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

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: k8s-launchpad # → infra/values/aks-dev/argocd-values.yaml (notifications.context.clusterName)
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.example.com # → infra/values/aks-dev/argocd-values.yaml (global.domain)
grafanaDomain: grafana.example.com # → infra/values/aks-dev/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.example.com # → infra/values/aks-dev/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.example.com # → infra/values/aks-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
dotaiUiDomain: kubemcpui.example.com # → infra/values/aks-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # → infra/values/aks-dev/traefik-values.yaml (ports.*.trustedIPs) — VNet CIDR + Azure health probe
cloudProvider: azure # → determines overlay directory and cloud-specific LB/storage annotations

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

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: prod-aks # → infra/values/aks-prod/argocd-values.yaml (notifications.context.clusterName)
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.example.com # → infra/values/aks-prod/argocd-values.yaml (global.domain)
grafanaDomain: grafana.example.com # → infra/values/aks-prod/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.example.com # → infra/values/aks-prod/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.example.com # → infra/values/aks-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
dotaiUiDomain: kubemcpui.example.com # → infra/values/aks-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # → infra/values/aks-prod/traefik-values.yaml (ports.*.trustedIPs) — VNet CIDR + Azure health probe
cloudProvider: azure # → determines overlay directory and cloud-specific LB/storage annotations

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

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: dev-eks # → infra/values/eks-dev/argocd-values.yaml (notifications.context.clusterName)
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.example.com # → infra/values/eks-dev/argocd-values.yaml (global.domain)
grafanaDomain: grafana.example.com # → infra/values/eks-dev/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.example.com # → infra/values/eks-dev/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.example.com # → infra/values/eks-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
dotaiUiDomain: kubemcpui.example.com # → infra/values/eks-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "10.0.0.0/8" # → infra/values/eks-dev/traefik-values.yaml (ports.*.trustedIPs) — VPC CIDR
cloudProvider: eks # → determines overlay directory and cloud-specific LB/storage annotations

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

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: prod-eks # → infra/values/eks-prod/argocd-values.yaml (notifications.context.clusterName)
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.example.com # → infra/values/eks-prod/argocd-values.yaml (global.domain)
grafanaDomain: grafana.example.com # → infra/values/eks-prod/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.example.com # → infra/values/eks-prod/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.example.com # → infra/values/eks-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
dotaiUiDomain: kubemcpui.example.com # → infra/values/eks-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "10.0.0.0/8" # → infra/values/eks-prod/traefik-values.yaml (ports.*.trustedIPs) — VPC CIDR
cloudProvider: eks # → determines overlay directory and cloud-specific LB/storage annotations

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

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: dev-gke # → infra/values/gke-dev/argocd-values.yaml (notifications.context.clusterName)
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.example.com # → infra/values/gke-dev/argocd-values.yaml (global.domain)
grafanaDomain: grafana.example.com # → infra/values/gke-dev/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.example.com # → infra/values/gke-dev/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.example.com # → infra/values/gke-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
dotaiUiDomain: kubemcpui.example.com # → infra/values/gke-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # → infra/values/gke-dev/traefik-values.yaml (ports.*.trustedIPs) — subnet + GCP health checks
cloudProvider: gke # → determines overlay directory and cloud-specific LB/storage annotations

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

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: prod-gke # → infra/values/gke-prod/argocd-values.yaml (notifications.context.clusterName)
domain: example.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.example.com # → infra/values/gke-prod/argocd-values.yaml (global.domain)
grafanaDomain: grafana.example.com # → infra/values/gke-prod/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.example.com # → infra/values/gke-prod/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.example.com # → infra/values/gke-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host) — create if needed
dotaiUiDomain: kubemcpui.example.com # → infra/values/gke-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host) — create if needed
letsencryptEmail: admin@example.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # → infra/values/gke-prod/traefik-values.yaml (ports.*.trustedIPs) — subnet + GCP health checks
cloudProvider: gke # → determines overlay directory and cloud-specific LB/storage annotations

12
clusters/upc-dev.yaml Normal file
View File

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: dev-fd-no-svg1 # → infra/values/upc-dev/argocd-values.yaml (notifications.context.clusterName)
domain: forteapps.net # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.127.0.0.1.nip.io # → infra/values/upc-dev/argocd-values.yaml (global.domain)
grafanaDomain: grafana.forteapps.net # → infra/values/upc-dev/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.forteapps.net # → infra/values/upc-dev/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.forteapps.net # → infra/values/upc-dev/dot-ai-stack-values.yaml (dot-ai.ingress.host)
dotaiUiDomain: kubemcpui.forteapps.net # → infra/values/upc-dev/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host)
letsencryptEmail: danijels@gmail.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "172.16.1.0/24" # → infra/values/upc-dev/traefik-values.yaml (ports.*.trustedIPs)
cloudProvider: upcloud # → determines overlay directory and cloud-specific LB/storage annotations

12
clusters/upc-prod.yaml Normal file
View File

@@ -0,0 +1,12 @@
# Cluster config reference — values must match the corresponding overlay files.
# Read by bootstrap.sh at install time; NOT auto-propagated to ArgoCD value files.
clusterName: prod-fd-no-svg1 # → infra/values/upc-prod/argocd-values.yaml (notifications.context.clusterName)
domain: fortedigital.com # → infra/values/base/gitea-values.yaml, renovate-values.yaml, keycloak-values.yaml (subdomains)
argocdDomain: argocd.127.0.0.1.nip.io # → infra/values/upc-prod/argocd-values.yaml (global.domain)
grafanaDomain: grafana.fortedigital.com # → infra/values/upc-prod/grafana-values.yaml (ingress.hosts)
keycloakDomain: id.fortedigital.com # → infra/values/upc-prod/keycloak-values.yaml (ingress.hostname)
dotaiDomain: kubemcp.fortedigital.com # → infra/values/upc-prod/dot-ai-stack-values.yaml (dot-ai.ingress.host)
dotaiUiDomain: kubemcpui.fortedigital.com # → infra/values/upc-prod/dot-ai-stack-values.yaml (dot-ai-ui.ingress.host)
letsencryptEmail: danijel.simeunovic@fortedigital.com # → cluster-resources/letsencrypt-issuer.yaml (spec.acme.email)
trustedIPs: "172.16.1.0/24" # → infra/values/upc-prod/traefik-values.yaml (ports.*.trustedIPs)
cloudProvider: upcloud # → determines overlay directory and cloud-specific LB/storage annotations

32
devbox.json Normal file
View File

@@ -0,0 +1,32 @@
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json",
"packages": [
"kubectl@1.33.2",
"kubernetes-helm@3.18.4",
"k9s@0.50.7",
"kubeseal@0.30.0",
"argocd@2.14.11",
"kubecm@0.33.1",
"kubectl-tree@0.4.3",
"kind@0.29.0",
"kustomize@5.7.0",
"kyverno@1.14.3",
"syft@1.29.0",
"grype@0.92.2",
"traefik@3.6.7",
"claude-code@latest",
"go@latest",
"dotnet-sdk@latest",
"opentofu@1.11.6"
],
"shell": {
"init_hook": [
"echo 'Welcome to devbox!' > /dev/null"
],
"scripts": {
"test": [
"echo \"Error: no test specified\" && exit 1"
]
}
}
}

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
@@ -654,21 +654,11 @@ kubectl create secret generic myapp-credentials \
#### Step 2: Seal the Secret #### Step 2: Seal the Secret
Get the public certificate (one-time setup):
```bash
# Fetch public cert from cluster
kubeseal --fetch-cert \
--controller-name=sealed-secrets-controller \
--controller-namespace=kube-system \
> pub-cert.pem
```
Seal your secret: Seal your secret:
```bash ```bash
kubeseal --format=yaml \ kubeseal --format=yaml \
--cert=pub-cert.pem \ --namespace=myapp \
< private/myapp-credentials.yaml \ < private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml > secrets/myapp-credentials-sealed.yaml
``` ```
@@ -683,7 +673,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:
@@ -711,7 +701,7 @@ kubectl create secret generic myapp-credentials \
# 2. Seal it # 2. Seal it
kubeseal --format=yaml \ kubeseal --format=yaml \
--cert=pub-cert.pem \ --namespace=myapp \
< private/myapp-credentials.yaml \ < private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml > secrets/myapp-credentials-sealed.yaml
@@ -791,7 +781,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 +903,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
@@ -962,6 +952,46 @@ User sees application (authenticated)
--- ---
### Accessing Authenticated User Information
The auth sidecar handles all authentication before requests reach your application. Your app never sees unauthenticated traffic — the sidecar returns 401 or redirects to the IdP first.
After successful authentication, the sidecar forwards the request to your application with user identity injected as HTTP headers:
| Header | Description | Available in |
|--------|-------------|-------------|
| `X-Auth-User` | Username or display name | Token, OIDC, MCP |
| `X-Auth-Email` | User email address | OIDC |
| `X-Auth-Subject` | OIDC `sub` claim (stable user ID) | OIDC, MCP |
| `X-Auth-Groups` | Comma-separated group memberships | OIDC (if scope includes `groups`) |
| `X-Auth-Token` | The validated access token | All modes |
**Your application reads these headers — no auth library needed:**
```javascript
// Express.js example
app.get('/profile', (req, res) => {
const user = req.headers['x-auth-user'];
const email = req.headers['x-auth-email'];
res.json({ user, email });
});
```
```python
# Flask example
@app.route('/profile')
def profile():
user = request.headers.get('X-Auth-User')
email = request.headers.get('X-Auth-Email')
return jsonify(user=user, email=email)
```
**Why this is safe**: The Kyverno-generated NetworkPolicy restricts ingress to the sidecar port only. Traffic cannot bypass the sidecar to reach the application port directly, so the `X-Auth-*` headers can be trusted unconditionally.
**Key principle**: Your application is zero-trust-unaware by design. It reads headers and renders UI. All authentication complexity lives in the sidecar and Kyverno policy.
---
### Authentication Configuration Reference ### Authentication Configuration Reference
#### Helm Values Schema #### Helm Values Schema
@@ -1049,7 +1079,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 +1107,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 +1142,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 +1166,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
@@ -1250,22 +1280,121 @@ kubectl logs -n myapp <pod-name> -c authn
## Adding a New Keycloak Client ## Adding a New Keycloak Client
When you need an application to authenticate via Keycloak (OIDC), you can add a client definition to the realm config. The secret syncer automatically extracts the Keycloak-generated client secret into a Kubernetes Secret that your application can reference — no manual secret management needed. There are two ways to add an OIDC client, depending on your use case:
### How It Works | Method | Best for | Who edits the infra repo? |
|--------|----------|--------------------------|
| **Self-service** (recommended) | New apps that deploy their own resources | App developer — no infra changes needed |
| **Legacy (realm JSON)** | Existing clients already defined in forte-realm.json (e.g., Gitea) | Platform engineer |
1. You define a client in `forte-realm.json` (inside `keycloak-values.yaml`) **without** a `secret` field Both methods are served by the **Keycloak Client Registrar** CronJob, which runs every 2 minutes.
2. Keycloak auto-generates a cryptographically strong secret on first creation
3. An ArgoCD **PostSync Job** (`keycloak-secret-syncer`) runs after each Keycloak sync:
- Authenticates to the Keycloak Admin API
- Finds clients with `k8s.secret.sync: "true"` in their attributes
- Extracts the auto-generated secret for each client
- Creates/updates a K8s Secret in the target namespace with `client-id` and `client-secret` keys
4. Your application references the syncer-created Secret
### Step 1: Add Client to Realm Config ### Self-Service OIDC Client Registration
In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array in `forte-realm.json`: This is the recommended flow for new applications. Your app deploys a labeled config Secret in its own namespace; the platform handles everything else.
#### How It Works
1. You deploy a Secret with label `keycloak.forteapps.net/client-config: "true"` containing a `client.json` definition
2. A **Kyverno ClusterPolicy** (`keycloak-client-config-cloner`) clones it to the `keycloak` namespace
3. The **Client Registrar CronJob** picks it up within 2 minutes:
- Registers (or updates) the client in Keycloak
- Fetches the auto-generated client secret
- Creates a credential Secret in your app's namespace
- Annotates the config Secret with sync status
#### Step 1: Create the Config Secret
Deploy this Secret in your application's namespace (e.g., as part of your Helm chart or Kustomize overlay):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-myapp
namespace: myapp
labels:
keycloak.forteapps.net/client-config: "true"
stringData:
client.json: |
{
"clientId": "myapp",
"name": "My Application",
"redirectUris": ["https://myapp.forteapps.net/*"],
"webOrigins": ["https://myapp.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "myapp",
"name": "myapp-oidc-credentials",
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
}
}
```
**`client.json` fields**:
| Field | Required | Description |
|-------|----------|-------------|
| `clientId` | Yes | Keycloak client ID |
| `name` | Yes | Display name in Keycloak |
| `redirectUris` | Yes | Allowed redirect URIs |
| `webOrigins` | Yes | Allowed web origins (CORS) |
| `defaultClientScopes` | No | Scopes (default: `["openid", "email", "profile"]`) |
| `protocolMappers` | No | Custom claim mappers (default: `[]`) |
| `secret.namespace` | No | Namespace for the credential Secret (default: source namespace) |
| `secret.name` | No | Name of the credential Secret (default: `<clientId>-oidc-credentials`) |
| `secret.keys.clientId` | No | Key name for client ID in credential Secret (default: `client-id`) |
| `secret.keys.clientSecret` | No | Key name for client secret in credential Secret (default: `client-secret`) |
#### Step 2: Reference the Credential Secret
In your application's deployment config, reference the credential Secret that the registrar creates:
```yaml
env:
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-id
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-secret
```
#### Step 3: Deploy and Wait
Commit and push your changes. The credential Secret will appear within 2 minutes:
```bash
# Watch for the credential Secret to be created
kubectl get secret myapp-oidc-credentials -n myapp -w
# Check registrar logs
kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
# Check sync status on the config Secret
kubectl get secret keycloak-client-myapp -n keycloak -o jsonpath='{.metadata.annotations}'
```
#### Change Detection
The registrar computes a SHA-256 hash of `client.json` and stores it as an annotation. On subsequent runs, it skips processing if:
- The hash hasn't changed, AND
- The credential Secret already exists in the target namespace
To force a re-sync, update any field in `client.json` (e.g., add a trailing space to `name`).
### Legacy Method: Realm JSON
Existing clients (like Gitea) are defined directly in `forte-realm.json` inside `keycloak-values.yaml`. The registrar syncs their secrets via client attributes.
#### Step 1: Add Client to Realm Config
In `infra/values/base/keycloak-values.yaml`, add a new entry to the `clients` array in `forte-realm.json`:
```json ```json
{ {
@@ -1292,59 +1421,27 @@ In `infra/values/keycloak-values.yaml`, add a new entry to the `clients` array i
**Important**: **Important**:
- Do **NOT** include a `"secret"` field — Keycloak generates one automatically - Do **NOT** include a `"secret"` field — Keycloak generates one automatically
- The `attributes` block tells the syncer where to create the K8s Secret - The `attributes` block tells the registrar where to create the K8s Secret
- The target namespace must exist before the syncer runs (ArgoCD creates it via `CreateNamespace=true`)
- Set `client-id-key` / `client-secret-key` to match what the consuming app expects (defaults: `client-id` / `client-secret`) - Set `client-id-key` / `client-secret-key` to match what the consuming app expects (defaults: `client-id` / `client-secret`)
### Step 2: Reference the Secret in Your Application #### Step 2: Reference the Secret in Your Application
In your application's Helm values, reference the syncer-created secret:
```yaml ```yaml
# In helm-values/myapp/values.yaml (or inline in values file)
# The secret will have keys: client-id, client-secret
existingSecret: myapp-oidc-credentials existingSecret: myapp-oidc-credentials
``` ```
For Gitea-style oauth config: #### Step 3: Commit and Push
```yaml
oauth:
- name: "Forte"
provider: "openidConnect"
existingSecret: myapp-oidc-credentials # Gitea expects "key" and "secret" as fields
autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration"
```
### Step 3: Commit and Push
```bash ```bash
cd ~/dev/k8s/launchpad cd ~/dev/k8s/launchpad
git add infra/values/keycloak-values.yaml git add infra/values/base/keycloak-values.yaml
git commit -m "Add myapp Keycloak client with auto-sync" git commit -m "Add myapp Keycloak client with auto-sync"
git push git push
``` ```
ArgoCD will: ArgoCD will sync the Keycloak config, and the registrar CronJob will pick up the new client within 2 minutes.
1. Sync the Keycloak config (keycloakConfigCli creates the client)
2. Run the PostSync syncer Job
3. The syncer creates `myapp-oidc-credentials` in the `myapp` namespace
### Step 4: Verify #### Legacy Sync Attribute Reference
```bash
# Check the syncer job ran successfully
kubectl get jobs -n keycloak
kubectl logs -n keycloak job/keycloak-secret-syncer
# Verify the secret was created
kubectl get secret myapp-oidc-credentials -n myapp -o yaml
# Check the secret has the expected keys
kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-id}' | base64 -d
kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-secret}' | base64 -d
```
### Sync Attribute Reference
| Attribute | Required | Default | Description | | Attribute | Required | Default | Description |
|-----------|----------|---------|-------------| |-----------|----------|---------|-------------|
@@ -1354,11 +1451,9 @@ kubectl get secret myapp-oidc-credentials -n myapp -o jsonpath='{.data.client-se
| `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret | | `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret |
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret | | `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret |
**Note on key names:** Different applications expect different field names. For example, the Gitea Helm chart expects `key` and `secret`, while a generic OIDC consumer might expect `client-id` and `client-secret`. Use the optional key attributes to match what the consuming application expects.
### Retrieving Secrets for External Deployments ### Retrieving Secrets for External Deployments
The syncer always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster: The registrar always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster:
```bash ```bash
# View the central copy # View the central copy
@@ -1369,16 +1464,13 @@ kubectl get secret myapp-oidc-credentials -n secrets \
-o jsonpath='{.data.client-secret}' | base64 -d -o jsonpath='{.data.client-secret}' | base64 -d
``` ```
This is useful when an application runs on a separate cluster or external infrastructure and needs the Keycloak-generated OIDC credentials provisioned manually (e.g., via a SealedSecret on the remote side). ### Registrar Behavior Notes
### Syncer Behavior Notes - The registrar runs as a CronJob every 2 minutes (`concurrencyPolicy: Forbid`)
- The syncer runs as an ArgoCD **PostSync hook** — it executes after all Keycloak resources are healthy
- `BeforeHookCreation` delete policy ensures old Job is cleaned up before each run
- If the target namespace doesn't exist, the target write is skipped with a warning (the central copy still happens) - If the target namespace doesn't exist, the target write is skipped with a warning (the central copy still happens)
- A central copy is **always** written to the `secrets` namespace for every synced client - A central copy is **always** written to the `secrets` namespace for every synced client
- The syncer uses the `keycloak-credentials` secret for admin authentication - The registrar uses the `keycloak-credentials` secret for admin authentication
- Created secrets have the label `app.kubernetes.io/managed-by: keycloak-secret-syncer` - Created secrets have the label `app.kubernetes.io/managed-by: keycloak-client-registrar`
--- ---
@@ -1438,7 +1530,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
``` ```
@@ -1587,7 +1679,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**: Single cluster, single environment - **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 │
┌────────────────────────────────┐ ┌────────────────────────────────┐
@@ -62,8 +62,8 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Kubernetes Cluster │ Kubernetes Clusters
│ (UpCloud Managed) │ (UpCloud, AWS, Azure, GCP)
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ ArgoCD │ │ │ │ ArgoCD │ │
@@ -116,81 +116,78 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
``` ```
launchpad/ launchpad/
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern) ├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev cluster)
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod cluster)
├── infra/ # Infrastructure ArgoCD Applications ├── infra/ # Infrastructure ArgoCD Applications (Kustomize)
│ ├── enterprise-apps.yaml # Parent app managing all apps in apps/ │ ├── base/ # Base Application manifests (one dir per component)
│ ├── cluster-resources-application.yaml │ ├── kustomization.yaml # Aggregates all component subdirectories
│ ├── traefik-application.yaml │ ├── traefik-application/
├── cert-manager-application.yaml │ │ ├── kustomization.yaml
├── kyverno.yaml │ │ └── traefik-application.yaml
│ ├── kyverno-policies.yaml │ ├── keycloak/
├── prometheus.yaml │ │ ├── kustomization.yaml
├── grafana.yaml │ │ └── keycloak.yaml
│ ├── loki.yaml │ ├── grafana/
│ ├── tempo.yaml │ ├── prometheus/
│ ├── fluent-bit.yaml │ ├── ... # Each component in its own subdirectory
├── trivy.yaml │ └── secrets/
│ ├── sealedsecrets.yaml │ ├── overlays/ # Per-cluster Kustomize overrides
│ ├── secrets.yaml │ ├── upc-dev/ # UpCloud Dev — includes all (resources: ../../base)
│ │ ├── upc-prod/ # UpCloud Prod — all + patches
│ │ ├── aks-dev/ # Azure AKS Dev — selective components
│ │ ├── aks-prod/ # Azure AKS Prod
│ │ ├── eks-dev/ # AWS EKS Dev
│ │ ├── eks-prod/ # AWS EKS Prod
│ │ ├── gke-dev/ # GCP GKE Dev
│ │ └── gke-prod/ # GCP GKE Prod
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides for infra │ └── values/ # Helm value overrides for infra
│ ├── argocd-values.yaml │ ├── base/ # Cloud-agnostic shared values
│ ├── prometheus-values.yaml │ ├── upc-{dev,prod}/ # UpCloud: storage class, LB, pricing
│ ├── grafana-values.yaml │ ├── aws-{dev,prod}/ # AWS: gp3, NLB, CUR pricing
│ ├── loki-values.yaml │ ├── aks-{dev,prod}/ # Azure: managed-csi-premium, Standard LB
── tempo-values.yaml ── gcp-{dev,prod}/ # GCP: premium-rwo, L4 LB
│ └── fluent-bit-values.yaml
├── apps/ # Business Application ArgoCD manifests ├── apps/ # Business Application ArgoCD manifests (Kustomize)
│ ├── mcp10x.yaml # MCP 10X application │ ├── base/ # One subdirectory per app
│ ├── musicman.yaml # Music Man application │ ├── kustomization.yaml
│ ├── dot-ai-stack.yaml # Dot AI Stack │ ├── musicman/
└── argo-mcp.yaml # ArgoCD MCP server │ ├── mcp10x/
│ │ ├── dot-ai-stack/
│ │ ├── ts-mcp/
│ │ └── argo-mcp/
│ └── overlays/
│ ├── upc-dev/ # All apps (resources: ../../base)
│ ├── upc-prod/ # All apps + patches
│ └── aks-dev/ # Selective apps only
├── cluster-resources/ # Cluster-wide Kubernetes resources ├── cluster-resources/ # Cluster-wide Kubernetes resources
│ ├── cert-manager-namespace.yaml │ ├── ...
│ ├── secrets-namespace.yaml
│ ├── letsencrypt-issuer.yaml # Let's Encrypt ClusterIssuer
│ ├── kyverno-config.yaml
│ ├── argocd-notifications-secret-sealed.yaml
│ ├── forte10x-repo-credentials-sealed.yaml
│ ├── mcp10x-repo-credentials-sealed.yaml
│ └── policies/ # Kyverno policies │ └── policies/ # Kyverno policies
│ ├── deployment-verifier.yaml
│ ├── label-checker.yaml
│ ├── bare-pod-cleaner.yaml
│ ├── replicaset-cleaner.yaml
│ ├── default-ns-blocker.yaml
│ ├── secret-cloner.yaml
│ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed) ├── secrets/ # Application secrets (sealed, per-cluster)
── argocd-mcp-credentials.yaml ── upc-dev/ # Secrets for upc-dev cluster
│ ├── dot-ai-secrets.yaml
│ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.yaml
├── private/ # Local-only files (NOT in Git) ├── private/ # Local-only files (NOT in Git)
│ ├── *.yaml # Unsealed secrets
│ └── *.sh # Helper scripts
└── docs/ # Documentation └── docs/ # Documentation
├── GITOPS-ARCHITECTURE.md # This file
├── DEVELOPER-GUIDE.md
├── OPERATIONS-RUNBOOK.md
└── REFERENCE.md
``` ```
**Key Points**: **Key Points**:
- `_app-of-apps.yaml` is the root Application that ArgoCD monitors - `_app-of-apps-upc-dev.yaml` and `_app-of-apps-upc-prod.yaml` are the per-cluster root Applications
- `infra/enterprise-apps.yaml` auto-discovers all apps in `apps/` folder - Each component in `base/` has its own subdirectory with a `kustomization.yaml`
- Overlays can include **all** components (`resources: [../../base]`) or **cherry-pick** specific ones (`resources: [../../base/grafana, ../../base/prometheus]`)
- Kustomize overlays in `infra/overlays/` render base Applications with per-cluster patches
- Helm values are split: `values/base/` (shared) + `values/upc-dev/` or `values/upc-prod/` (cluster-specific)
- `apps/` follows the same base/overlays pattern for business applications
- Changes pushed to this repo trigger automatic syncs in ArgoCD - Changes pushed to this repo trigger automatic syncs in ArgoCD
- `private/` folder contains local-only files (Git-ignored) - `private/` folder contains local-only files (Git-ignored)
--- ---
### 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`
@@ -224,7 +221,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`
@@ -234,8 +231,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
``` ```
@@ -285,7 +280,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
--- ---
@@ -295,7 +290,7 @@ app-repository/
### The App-of-Apps Pattern ### The App-of-Apps Pattern
``` ```
_app-of-apps.yaml (Root) _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
@@ -315,10 +310,10 @@ _app-of-apps.yaml (Root)
``` ```
**How It Works**: **How It Works**:
1. Bootstrap script installs ArgoCD and applies `_app-of-apps.yaml` 1. Bootstrap script installs ArgoCD and applies `_app-of-apps-upc-dev.yaml` (or `upc-prod`)
2. ArgoCD creates the root Application which monitors `infra/` folder 2. ArgoCD creates the root Application which monitors the appropriate `infra/overlays/` folder
3. Each YAML in `infra/` becomes a child Application 3. Kustomize renders base Applications with cluster-specific patches
4. `enterprise-apps.yaml` monitors `apps/` folder and auto-discovers applications 4. `enterprise-apps` Application monitors the cluster's `apps/overlays/` folder
5. ArgoCD continuously syncs (every 60s) and auto-heals drift 5. ArgoCD continuously syncs (every 60s) and auto-heals drift
### Sync Waves & Ordering ### Sync Waves & Ordering
@@ -346,13 +341,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
``` ```
@@ -363,6 +358,57 @@ spec:
- Easy to update all apps by changing the chart - Easy to update all apps by changing the chart
- Environment-specific values isolated in separate repo - Environment-specific values isolated in separate repo
### Multi-Cluster Pattern
Kustomize overlays enable deploying the same Applications across clusters with different configurations.
Each component in `infra/base/` and `apps/base/` lives in its own subdirectory. Overlays define **which components to include** and optionally **patch** them:
```yaml
# Option 1: Include ALL components (full cluster)
# infra/overlays/upc-dev/kustomization.yaml
resources:
- ../../base # Pulls in every component subdirectory
# Option 2: Cherry-pick specific components (lightweight cluster)
# infra/overlays/aks-dev/kustomization.yaml
resources:
- ../../base/traefik-application
- ../../base/grafana
- ../../base/prometheus
- ../../base/loki
# Only listed components are deployed — others are excluded
```
Per-cluster patches swap Helm value file paths:
```yaml
# infra/overlays/upc-prod/kustomization.yaml
patches:
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/traefik-values.yaml
```
Cloud-specific values (storage classes, load balancer annotations, cost model) are isolated in per-cluster value files. Base values are fully cloud-agnostic:
| Cloud | Storage Class | Load Balancer | OpenCost Provider |
|-------|--------------|---------------|-------------------|
| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud LB (ProxyProtocol v2) | Custom pricing |
| **AWS EKS** | `gp3` (EBS CSI) | NLB (ProxyProtocol v2) | AWS CUR |
| **Azure AKS** | `managed-csi-premium` | Standard LB (`externalTrafficPolicy: Local`) | Azure Billing API |
| **GCP GKE** | `premium-rwo` (PD CSI) | L4 passthrough NLB | GCP Cloud Billing |
**Benefits**:
- Single source of truth for Application definitions
- Cluster-specific values isolated per overlay
- Easy to add new clusters by creating a new overlay
- Base values shared across all clusters reduce duplication
--- ---
## CI/CD Pipeline ## CI/CD Pipeline
@@ -392,8 +438,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
@@ -410,7 +456,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
@@ -617,7 +663,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
@@ -638,6 +684,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**
@@ -207,7 +210,7 @@ kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository
# Settings → Repositories → Should show "Successful" status # Settings → Repositories → Should show "Successful" status
# Test by creating an application # Test by creating an application
kubectl apply -f _app-of-apps.yaml kubectl apply -f _app-of-apps-upc-dev.yaml # or _app-of-apps-upc-prod.yaml
# Check application sync status # Check application sync status
kubectl get applications -n argocd kubectl get applications -n argocd
@@ -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
@@ -1352,13 +1363,13 @@ kubectl get deployment argocd-server -n argocd \
-o jsonpath='{.spec.template.spec.containers[0].image}' -o jsonpath='{.spec.template.spec.containers[0].image}'
# Update version in values # Update version in values
vim infra/values/argocd-values.yaml vim infra/values/base/argocd-values.yaml
# Or upgrade via Helm directly # Or upgrade via Helm directly
helm upgrade argocd argo-cd \ helm upgrade argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--namespace argocd \ --namespace argocd \
--values infra/values/argocd-values.yaml \ --values infra/values/base/argocd-values.yaml \
--version 6.0.0 # New version --version 6.0.0 # New version
# Verify # Verify
@@ -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
@@ -1454,8 +1468,8 @@ kubectl top pods --all-namespaces --sort-by=cpu
Example: Adding Redis Example: Adding Redis
```bash ```bash
# 1. Create application manifest # 1. Create application manifest in base/
cat > infra/redis-application.yaml <<EOF cat > infra/base/redis-application.yaml <<EOF
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
@@ -1465,15 +1479,17 @@ metadata:
argocd.argoproj.io/sync-wave: "1" argocd.argoproj.io/sync-wave: "1"
spec: spec:
project: default project: default
source: sources:
repoURL: https://charts.bitnami.com/bitnami - repoURL: https://charts.bitnami.com/bitnami
chart: redis chart: redis
targetRevision: 18.0.0 targetRevision: 18.0.0
helm: helm:
values: | releaseName: redis
auth: valueFiles:
enabled: true - \$values/infra/values/base/redis-values.yaml
password: changeme - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: redis namespace: redis
@@ -1485,30 +1501,54 @@ spec:
- CreateNamespace=true - CreateNamespace=true
EOF EOF
# 2. Commit and push # 2. Add to base kustomization
git add infra/redis-application.yaml # Edit infra/base/kustomization.yaml and add: - redis-application.yaml
# 3. Create base values file
cat > infra/values/base/redis-values.yaml <<EOF
auth:
enabled: true
EOF
# 4. Commit and push
git add infra/base/redis-application.yaml infra/values/base/redis-values.yaml infra/base/kustomization.yaml
git commit -m "Add Redis infrastructure component" git commit -m "Add Redis infrastructure component"
git push git push
# 3. ArgoCD will auto-sync within 60 seconds # 5. ArgoCD will auto-sync within 60 seconds
``` ```
### Multi-Cluster Setup (Future) ### Multi-Cluster Setup
For multi-cluster deployments: The repository supports multiple clusters across multiple clouds via Kustomize overlays:
```yaml **Active clusters:**
# Different destinations per environment - **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is
# dev-cluster - **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod`
destination:
server: https://dev.k8s.example.com
namespace: myapp
# prod-cluster **Cloud-ready templates (fill in `clusters/*.yaml` before use):**
destination: - **eks-dev** / **eks-prod**: AWS EKS with NLB, gp3 storage, AWS CUR pricing
server: https://prod.k8s.example.com - **aks-dev** / **aks-prod**: Azure AKS with Standard LB, managed-csi-premium storage
namespace: myapp - **gke-dev** / **gke-prod**: GCP GKE with L4 LB, premium-rwo storage
```
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
@@ -1552,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
@@ -1625,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
@@ -1651,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 Cluster (UpCloud) │ 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 - **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** | Single cluster | | **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]
@@ -71,42 +73,62 @@ Internet
``` ```
launchpad/ launchpad/
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application ├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev)
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod)
├── infra/ # Infrastructure applications ├── infra/ # Infrastructure applications (Kustomize)
│ ├── cluster-resources-application.yaml │ ├── base/ # One subdirectory per component
│ ├── enterprise-apps.yaml │ ├── kustomization.yaml # Aggregates all component subdirectories
│ ├── traefik-application.yaml │ ├── traefik-application/
├── cert-manager-application.yaml │ │ ├── kustomization.yaml
├── kyverno.yaml │ │ └── traefik-application.yaml
│ ├── kyverno-policies.yaml │ ├── keycloak/
├── prometheus.yaml │ │ ├── kustomization.yaml
├── grafana.yaml │ │ └── keycloak.yaml
│ ├── loki.yaml │ ├── grafana/
│ ├── tempo.yaml │ ├── prometheus/
│ ├── fluent-bit.yaml │ ├── loki/
│ ├── trivy.yaml │ ├── tempo/
│ ├── gitea.yaml │ ├── gitea/
│ ├── gitea-actions.yaml │ ├── opencost/
│ ├── sealedsecrets.yaml │ ├── ... # Each component in own directory
── secrets.yaml │ └── secrets/
│ ├── renovate.yaml │ ├── overlays/ # Per-cluster: include all or cherry-pick
│ │ ├── upc-dev/ # resources: [../../base] (all components)
│ │ ├── upc-prod/ # resources: [../../base] + patches
│ │ ├── aks-dev/ # resources: [../../base/grafana, ...] (selective)
│ │ └── .../ # 8 clusters total
│ └── values/ │ └── values/
│ ├── argocd-values.yaml │ ├── base/ # Cloud-agnostic Helm values
├── prometheus-values.yaml │ ├── gitea-values.yaml
│ │ ├── opencost-values.yaml
│ │ ├── prometheus-values.yaml
│ │ └── ...
│ ├── upc-dev/ # UpCloud dev overlay values
│ │ ├── traefik-values.yaml
│ │ ├── keycloak-values.yaml
│ │ ├── grafana-values.yaml
│ │ ├── gitea-values.yaml
│ │ └── opencost-values.yaml
│ └── upc-prod/ # UpCloud prod overlay values
│ ├── traefik-values.yaml
│ ├── keycloak-values.yaml
│ ├── grafana-values.yaml │ ├── grafana-values.yaml
│ ├── loki-values.yaml
│ ├── tempo-values.yaml
│ ├── gitea-values.yaml │ ├── gitea-values.yaml
├── gitea-actions-values.yaml └── opencost-values.yaml
│ ├── fluent-bit-values.yaml
│ └── renovate-values.yaml
├── apps/ # Business applications ├── apps/ # Business applications (Kustomize)
│ ├── mcp10x.yaml │ ├── base/ # One subdirectory per app
│ ├── musicman.yaml │ ├── kustomization.yaml
│ ├── dot-ai-stack.yaml │ ├── musicman/
└── argo-mcp.yaml │ ├── mcp10x/
│ │ ├── dot-ai-stack/
│ │ ├── ts-mcp/
│ │ └── argo-mcp/
│ └── overlays/ # Per-cluster: include all or cherry-pick
│ ├── upc-dev/
│ ├── upc-prod/
│ └── aks-dev/ # Selective apps only
├── cluster-resources/ # Cluster-level resources ├── cluster-resources/ # Cluster-level resources
│ ├── cert-manager-namespace.yaml │ ├── cert-manager-namespace.yaml
@@ -123,15 +145,43 @@ launchpad/
│ ├── replicaset-cleaner.yaml │ ├── replicaset-cleaner.yaml
│ ├── default-ns-blocker.yaml │ ├── default-ns-blocker.yaml
│ ├── secret-cloner.yaml │ ├── secret-cloner.yaml
│ ├── keycloak-client-cloner.yaml
│ └── auth-sidecar-injector.yaml │ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed) ├── secrets/ # Application secrets (sealed)
│ ├── argocd-mcp-credentials.yaml │ ├── base/ # All SealedSecrets (shared across clouds)
│ ├── dot-ai-secrets.yaml │ ├── kustomization.yaml
│ ├── gitea-credentials-sealed.yaml │ ├── argocd-forte-helm-secret-sealed.yaml
│ ├── gitea-runner-token-sealed.yaml │ ├── argocd-mcp-credentials.yaml
│ ├── mcp10x-credentials-sealed.yaml │ ├── argocdmcp-auth-oidc-sealed.yaml
└── musicman-credentials.yaml │ ├── dot-ai-secrets.yaml
│ │ ├── forte10x-app-credentials-sealed.yaml
│ │ ├── gitea-backup-s3-sealed.yaml
│ │ ├── gitea-credentials-sealed.yaml
│ │ ├── gitea-runner-token-sealed.yaml
│ │ ├── gitea-smtp-secret-sealed.yaml
│ │ ├── keycloak-credentials-sealed.yaml
│ │ ├── musicman-auth-oidc-sealed.yaml
│ │ ├── musicman-credentials.yaml
│ │ └── renovate-env-sealed.yaml
│ └── overlays/ # Per-cloud overlays (reference base)
│ ├── aks-dev/kustomization.yaml
│ ├── aks-prod/kustomization.yaml
│ ├── eks-dev/kustomization.yaml
│ ├── eks-prod/kustomization.yaml
│ ├── gke-dev/kustomization.yaml
│ ├── gke-prod/kustomization.yaml
│ ├── upc-dev/kustomization.yaml
│ └── upc-prod/kustomization.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
@@ -155,15 +205,15 @@ ArgoCd() {
helm upgrade --install argocd argo-cd \ helm upgrade --install argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--namespace argocd --create-namespace \ --namespace argocd --create-namespace \
--values infra/values/argocd-values.yaml \ --values infra/values/base/argocd-values.yaml \
--set notifications.context.clusterName="$CLUSTER_NAME" \ --set notifications.context.clusterName="$CLUSTER_NAME" \
--timeout 60s --atomic --timeout 60s --atomic
kubectl apply -f _app-of-apps.yaml -n argocd kubectl apply -f _app-of-apps-upc-dev.yaml -n argocd # or _app-of-apps-upc-prod.yaml
} }
``` ```
**`_app-of-apps.yaml`** **`_app-of-apps-upc-dev.yaml`** / **`_app-of-apps-upc-prod.yaml`**
```yaml ```yaml
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
@@ -188,7 +238,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`
@@ -335,20 +385,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
``` ```
@@ -524,14 +572,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
@@ -602,10 +650,134 @@ retry:
4. 40 seconds 4. 40 seconds
5. 80 seconds (capped at 3 minutes) 5. 80 seconds (capped at 3 minutes)
### Global Settings (`argocd-cm`)
| Setting | Value | Purpose |
|---------|-------|---------|
| `application.resourceTrackingMethod` | `annotation` | Track resources via annotations |
| `timeout.reconciliation` | `60s` | Reconciliation interval |
| `admin.enabled` | `false` | Admin login disabled (SSO-only) |
| `url` | `https://argocd.forteapps.net` | External URL for ArgoCD UI |
**Git Submodule Disable**: Set via `configs.params` (NOT `repoServer.env` — that causes strategic merge conflicts with chart's `valueFrom` entries):
```yaml
configs:
params:
"reposerver.enable.git.submodule": "false"
```
This writes to `argocd-cmd-params-cm` ConfigMap, which the chart already reads via `valueFrom`. Submodules (e.g., `shared-prompts`) are not needed for K8s manifest generation.
**Break-Glass Admin Access**: Admin login is disabled (`admin.enabled: false`). The admin password remains in `argocd-secret`. To re-enable temporarily:
```bash
# Enable admin login
kubectl patch cm argocd-cm -n argocd -p '{"data":{"admin.enabled":"true"}}'
# Log in as admin, do what's needed, then disable again
kubectl patch cm argocd-cm -n argocd -p '{"data":{"admin.enabled":"false"}}'
```
ArgoCD picks up ConfigMap changes within the reconciliation timeout (60s). Note: ArgoCD will revert this on next sync — this is intentional (temporary access only).
**OIDC Authentication** (Keycloak):
```yaml
configs:
cm:
oidc.config: |
name: Forte SSO
issuer: https://id.forteapps.net/realms/forte
clientID: argocd
clientSecret: $oidc.clientSecret
requestedScopes: ["openid", "email", "profile"]
rbacConfig:
policy.csv: |
g, ArgoCD Admins, role:admin
g, ArgoCD Viewers, role:readonly
# Deny users not in any declared KC group
policy.default: ""
scopes: '[groups]'
```
**Access Control**: Only users in Keycloak groups `ArgoCD Admins` or `ArgoCD Viewers` can access ArgoCD. Users not in either group are denied (empty `policy.default`). Assign users to groups in Keycloak admin console.
- ArgoCD does NOT add `openid` implicitly — must include in `requestedScopes`
- Do NOT add `groups` as a scope — the KC groups mapper emits the claim regardless
- `$oidc.clientSecret` references the `oidc.clientSecret` key in `argocd-secret`
- OIDC secret is synced by CronJob `argocd-oidc-sync` (see `cluster-resources/argocd-oidc-secret-sync.yaml`)
- The CronJob bridges `argocd-oidc-credentials` (from KC registrar) → `argocd-secret` every 2 min
- Safe for fresh deploys: no-ops if source secret doesn't exist yet
**Ingress** (Traefik + TLS):
```yaml
server:
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
tls: true
extraArgs:
- --insecure
configs:
params:
"server.insecure": true
```
TLS terminates at Traefik; ArgoCD runs in insecure mode behind the proxy.
--- ---
## Infrastructure Components ## Infrastructure Components
### Homepage (Platform Dashboard)
**Chart**: `jameswynn/homepage`
**Namespace**: `homepage`
**URL**: `https://start.forteapps.net`
Platform dashboard that auto-discovers deployed apps via Kubernetes service annotations.
**Discovery mechanism**: Services annotated with `gethomepage.dev/enabled: "true"` appear in the dashboard. Apps not deployed = annotations absent = not shown. Fully dynamic per environment.
**Annotated services**:
| Service | Namespace | Group | Widget |
|---------|-----------|-------|--------|
| `gitea-http` | `gitea` | DevOps | `gitea` |
| `argocd-server` | `argocd` | DevOps | `argocd` |
| `keycloak` | `keycloak` | Identity | none |
| `grafana` | `monitoring` | Monitoring | `grafana` |
| `karpor-server` | `karpor` | DevOps | none |
**Adding a new app**: Annotate the app's Service in its Helm values:
```yaml
service:
annotations:
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "My App"
gethomepage.dev/description: "What it does"
gethomepage.dev/group: "GroupName"
gethomepage.dev/icon: "icon-name" # https://github.com/walkxcode/dashboard-icons
gethomepage.dev/href: "https://myapp.forteapps.net"
# Optional live widget:
gethomepage.dev/widget.type: "myapp"
gethomepage.dev/widget.url: "https://myapp.forteapps.net"
# gethomepage.dev/widget.key: "{{HOMEPAGE_VAR_MYAPP_TOKEN}}"
```
**Widget API credentials**: Inject via env vars into the Homepage pod:
```yaml
# In homepage-values.yaml per environment
env:
- name: HOMEPAGE_VAR_GRAFANA_TOKEN
valueFrom:
secretKeyRef:
name: homepage-widget-credentials
key: grafana-token
```
Then reference as `gethomepage.dev/widget.key: "{{HOMEPAGE_VAR_GRAFANA_TOKEN}}"`.
**Values files**:
- `infra/values/base/homepage-values.yaml` — RBAC, kubernetes mode, layout
- `infra/values/{env}/homepage-values.yaml` — hostname per environment
---
### Traefik ### Traefik
**Chart**: `traefik/traefik` **Chart**: `traefik/traefik`
@@ -614,7 +786,7 @@ retry:
**Configuration**: **Configuration**:
```yaml ```yaml
# infra/traefik-application.yaml # infra/base/traefik-application.yaml
replicas: 2 replicas: 2
service: service:
@@ -677,6 +849,10 @@ spec:
**Chart**: `sealed-secrets/sealed-secrets-controller` **Chart**: `sealed-secrets/sealed-secrets-controller`
**Namespace**: `kube-system` **Namespace**: `kube-system`
**Directory Structure**: `secrets/base/` contains all SealedSecrets with a `kustomization.yaml`. Per-cloud overlays in `secrets/overlays/<cloud>/` reference the base via Kustomize. The ArgoCD `secrets` Application points to the active overlay (e.g., `secrets/overlays/upc-dev`), and `infra/overlays/upc-prod` patches the path to `secrets/overlays/upc-prod`.
To add cloud-specific secrets, create a new SealedSecret in the overlay directory and add it to the overlay's `kustomization.yaml`.
**Public Certificate**: **Public Certificate**:
```bash ```bash
kubeseal --fetch-cert \ kubeseal --fetch-cert \
@@ -717,6 +893,15 @@ kubeStateMetrics:
- Loki - Loki
- Tempo - Tempo
**Ingress**: Exposed via Traefik at `https://grafana.forteapps.net` with cert-manager TLS.
**OIDC Authentication** (Keycloak):
- Uses `grafana.ini.auth.generic_oauth` with KC `grafana` client
- Secret `grafana-oidc-credentials` synced by KC registrar, loaded via `envFromSecrets`
- SSO-only mode: `auth.disable_login_form: true` + `auth.generic_oauth.auto_login: true`
- Role mapping via JMESPath on `resource_access.grafana.roles` claim (requires KC client role mapper)
- Roles: KC client roles `Admin`/`Editor` map to Grafana roles; default is `Viewer`
### Loki ### Loki
**Chart**: `grafana/loki-stack` **Chart**: `grafana/loki-stack`
@@ -789,7 +974,7 @@ persistence:
**Configuration**: **Configuration**:
```yaml ```yaml
# infra/gitea.yaml + infra/values/gitea-values.yaml # infra/base/gitea.yaml + infra/values/base/gitea-values.yaml
ingress: ingress:
host: git.forteapps.net host: git.forteapps.net
tls: cert-manager (letsencrypt-prod) tls: cert-manager (letsencrypt-prod)
@@ -815,12 +1000,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
@@ -832,7 +1026,7 @@ postgresql:
**Configuration**: **Configuration**:
```yaml ```yaml
# infra/gitea-actions.yaml + infra/values/gitea-actions-values.yaml # infra/base/gitea-actions.yaml + infra/values/base/gitea-actions-values.yaml
replicaCount: 3 replicaCount: 3
runner: runner:
@@ -869,29 +1063,192 @@ 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
### Keycloak Secret Syncer ### Vaultwarden
**Type**: ArgoCD PostSync Job (deployed via Keycloak Helm chart `extraDeploy`) **Chart**: `guerzon/vaultwarden`
**Version**: 0.36.4 (app v1.36.0-alpine)
**Namespace**: `vaultwarden`
**Purpose**: Self-hosted Bitwarden-compatible password manager.
**Configuration**:
```yaml
# infra/overlays/upc-dev/vaultwarden/ + infra/values/
domain: "https://bitwarden.forteapps.net"
ingress:
enabled: true
class: "traefik"
tls: true
tlsSecret: vaultwarden-tls
hostname: bitwarden.forteapps.net
additionalAnnotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
database:
type: postgresql
host: vaultwarden-postgresql # StatefulSet in overlay
existingSecret: prod-db-creds
storage:
data: 5Gi (ReadWriteOnce)
attachments: 5Gi (ReadWriteOnce)
```
**TLS**: cert-manager auto-provisions Let's Encrypt certificate via `letsencrypt-prod` ClusterIssuer (same pattern as Gitea, Grafana, etc).
**SSO**: Keycloak OIDC via `forte` realm (client ID: `vaultwarden`). Self-service client config Secret (`keycloak-client-vaultwarden`) triggers registrar to create KC client and sync credentials to `vaultwarden-oidc-credentials`. PKCE enabled.
**Endpoints**:
- Web UI: `https://bitwarden.forteapps.net`
**Database**: Separate ArgoCD Application `vaultwarden-postgresql` (sync-wave `"0"`) deploys PostgreSQL 16 StatefulSet + SealedSecret before Vaultwarden (wave `"1"`). 2Gi PVC. Chart does NOT include a PostgreSQL subchart — must be provisioned separately.
**Secrets**:
- `prod-db-creds` (SealedSecret) — PostgreSQL credentials (`pgusername`, `pgpassword`) + SMTP credentials
- `vaultwarden-oidc-credentials` (registrar-managed) — OIDC client ID + secret
- `vaultwarden-tls` — auto-managed by cert-manager
### 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 Browser Flow (IdP Auto-Redirect)
**File**: `infra/values/base/keycloak-values.yaml` (inside `forte-realm.json`)
The realm uses a custom browser authentication flow (`browser-auto-idp`) that skips the Keycloak login page and redirects directly to the Entra ID identity provider.
**Flow executions**:
| Priority | Authenticator | Requirement | Purpose |
|----------|--------------|-------------|---------|
| 10 | `auth-cookie` | ALTERNATIVE | Reuse existing session (no redirect) |
| 20 | `identity-provider-redirector` | ALTERNATIVE | Auto-redirect to `forte-entra` IdP |
**Key fields in realm JSON**:
- `"browserFlow": "browser-auto-idp"` — overrides the default `browser` flow at realm level
- `"authenticationFlows"` — defines the custom flow with its executions
- `"authenticatorConfig"` — sets `defaultProvider: "forte-entra"` on the redirector
**Why custom flow**: The default KC browser flow shows a username/password form with an IdP button. Since all authentication is via Entra ID, the custom flow eliminates this step. The `auth-cookie` execution preserves session reuse so returning users aren't redirected again.
**Important**: The `forte-entra` identity provider must exist in Keycloak (currently configured manually in the KC admin console). If the IdP alias changes, update the `defaultProvider` value in the realm JSON.
---
### Keycloak Client Registrar
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
**Namespace**: `keycloak` **Namespace**: `keycloak`
**Schedule**: `*/2 * * * *` (every 2 minutes)
**Purpose**: Automatically extracts Keycloak-generated client secrets and syncs them into Kubernetes Secrets in target namespaces. Eliminates the need to manually manage OIDC client secrets. **Purpose**: Handles two responsibilities:
1. **Legacy sync** — extracts secrets from Keycloak clients with `k8s.secret.sync: "true"` attribute (same as former PostSync syncer)
2. **Self-service registration** — processes config Secrets (cloned by Kyverno) to register new OIDC clients and sync their credentials
**How It Works**: **How It Works**:
1. Runs as an ArgoCD PostSync hook after Keycloak resources are healthy
2. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret *Legacy path (existing clients like Gitea):*
3. Queries all clients in the `forte` realm 1. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret
4. Filters clients with `k8s.secret.sync: "true"` attribute 2. Queries all clients in the `forte` realm
5. For each matching client, retrieves the auto-generated secret via Keycloak Admin API 3. Filters clients with `k8s.secret.sync: "true"` attribute
6. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute) 4. For each matching client, retrieves the auto-generated secret via Keycloak Admin API
7. Always writes a central copy to the `secrets` namespace (for external deployment retrieval) 5. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute)
6. Always writes a central copy to the `secrets` namespace
*Self-service path (new clients):*
1. Lists Secrets in `keycloak` namespace with label `keycloak.forteapps.net/client-config=true`
2. For each config Secret, parses `client.json` and computes a config hash
3. Skips if hash matches annotation and credential Secret already exists
4. Creates or updates the Keycloak client via Admin API
5. Fetches the generated client secret
6. Upserts credential Secret in target namespace + central `secrets` namespace
7. Annotates config Secret with sync status, config hash, and timestamp
**Resources**: **Resources**:
- `ServiceAccount`: `keycloak-secret-syncer` (namespace: `keycloak`) - `ServiceAccount`: `keycloak-client-registrar` (namespace: `keycloak`)
- `ClusterRole`: `keycloak-secret-syncer` (secrets: get/create/update/patch; namespaces: get/list) - `ClusterRole`: `keycloak-client-registrar` (secrets: get/list/create/update/patch; namespaces: get/list)
- `ClusterRoleBinding`: `keycloak-secret-syncer` - `ClusterRoleBinding`: `keycloak-client-registrar`
- `Job`: `keycloak-secret-syncer` (PostSync hook) - `CronJob`: `keycloak-client-registrar`
**Client Attributes** (set in `forte-realm.json`): **Kyverno Policy**: `keycloak-client-config-cloner` — clones labeled Secrets from app namespaces to `keycloak` namespace (see [Kyverno Policies](#kyverno-policies))
**Legacy Client Attributes** (set in `forte-realm.json`):
| Attribute | Required | Default | Description | | Attribute | Required | Default | Description |
|-----------|----------|---------|-------------| |-----------|----------|---------|-------------|
@@ -901,35 +1258,99 @@ dind:
| `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret | | `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret |
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret | | `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret |
**Created Secret Format** (key names configurable via attributes): **Self-Service Config Secret Schema**:
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: <k8s.secret.name> name: keycloak-client-<app>
namespace: <k8s.secret.namespace> namespace: <app-namespace>
labels: labels:
app.kubernetes.io/managed-by: keycloak-secret-syncer keycloak.forteapps.net/client-config: "true"
stringData:
client.json: |
{
"clientId": "<app>",
"name": "<App Name>",
"redirectUris": ["https://<app>.forteapps.net/*"],
"webOrigins": ["https://<app>.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "<app-namespace>",
"name": "<app>-oidc-credentials",
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
}
}
```
**Created Credential Secret Format**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: <target-name>
namespace: <target-namespace>
labels:
app.kubernetes.io/managed-by: keycloak-client-registrar
type: Opaque type: Opaque
data: data:
<client-id-key>: <base64-encoded client ID> <client-id-key>: <base64-encoded client ID>
<client-secret-key>: <base64-encoded client secret> <client-secret-key>: <base64-encoded client secret>
``` ```
**Config Secret Annotations** (set by registrar):
| Annotation | Description |
|-----------|-------------|
| `keycloak.forteapps.net/config-hash` | SHA-256 hash of client.json for change detection |
| `keycloak.forteapps.net/sync-status` | `synced` or `error` |
| `keycloak.forteapps.net/last-sync` | ISO 8601 timestamp of last successful sync |
**Verification**: **Verification**:
```bash ```bash
# Check job status # Check CronJob status
kubectl get jobs -n keycloak kubectl get cronjobs -n keycloak
# View syncer logs # View latest registrar logs
kubectl logs -n keycloak job/keycloak-secret-syncer kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
# Verify created secret # Verify created secret
kubectl get secret <name> -n <namespace> -o yaml kubectl get secret <name> -n <namespace> -o yaml
# Check config Secret annotations (self-service)
kubectl get secret keycloak-client-<app> -n keycloak -o jsonpath='{.metadata.annotations}'
``` ```
**See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client) **See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client)
### Karpor
**Chart**: `karpor` from `https://kusionstack.github.io/charts`
**Version**: 0.7.6 (app v0.6.4)
**Namespace**: `karpor`
**Sync Wave**: 1
**Purpose**: Kubernetes visualization and intelligence tool. Provides cross-cluster resource search, compliance checking, and topology visualization. Gives platform engineers a unified view of all cluster resources and their relationships.
**Architecture** (4 components):
- **Server** — main Karpor API/UI (port 7443)
- **Syncer** — syncs cluster state into the search index
- **ElasticSearch** — search backend for resource indexing
- **etcd** — persistent key-value store (10Gi PVC)
**Configuration** (`infra/values/base/karpor-values.yaml`):
- `namespaceEnabled: false` — ArgoCD manages namespace creation
- Default resource limits tuned for small clusters
- ElasticSearch: 2 CPU / 4Gi memory (the heaviest component)
- AI features available but not enabled (requires `server.ai.authToken` + backend config)
**Access**: Port-forward to reach the UI:
```bash
kubectl port-forward svc/karpor-release-server -n karpor 7443:7443
# Open https://localhost:7443
```
### Renovate ### Renovate
**Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`) **Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`)
@@ -941,7 +1362,7 @@ kubectl get secret <name> -n <namespace> -o yaml
**Configuration**: **Configuration**:
```yaml ```yaml
# infra/renovate.yaml + infra/values/renovate-values.yaml # infra/base/renovate.yaml + infra/values/base/renovate-values.yaml
cronjob: cronjob:
schedule: "@daily" schedule: "@daily"
concurrencyPolicy: Forbid concurrencyPolicy: Forbid
@@ -1020,6 +1441,19 @@ spec:
**Label Requirement**: Secrets must have `allowedToBeCloned: "true"` **Label Requirement**: Secrets must have `allowedToBeCloned: "true"`
### Keycloak Client Config Cloner
**File**: `cluster-resources/policies/keycloak-client-cloner.yaml`
**Purpose**: Clones Secrets labeled `keycloak.forteapps.net/client-config: "true"` from app namespaces to the `keycloak` namespace. This allows apps to declare their OIDC client configuration in their own namespace, which the [Keycloak Client Registrar](#keycloak-client-registrar) then processes.
**Trigger**: Any Secret with label `keycloak.forteapps.net/client-config: "true"` created outside the `keycloak` namespace.
**Behavior**:
- Generates a copy of the Secret in the `keycloak` namespace with the same name
- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-name`)
- `synchronize: true` — changes to the source Secret are reflected in the clone
### Default Namespace Blocker ### Default Namespace Blocker
**File**: `cluster-resources/policies/default-ns-blocker.yaml` **File**: `cluster-resources/policies/default-ns-blocker.yaml`
@@ -1364,7 +1798,23 @@ Forward to Application (localhost:3000)
Application processes request Application processes request
``` ```
**See**: [Developer Guide - Enabling Authentication](DEVELOPER-GUIDE.md#enabling-authentication-for-applications) for usage examples. #### Forwarded Headers
After successful authentication, the sidecar injects user identity as HTTP headers before forwarding the request to the application container:
| Header | Description | Auth Modes |
|--------|-------------|------------|
| `X-Auth-User` | Username or display name | Token, OIDC, MCP |
| `X-Auth-Email` | User email address | OIDC |
| `X-Auth-Subject` | OIDC `sub` claim (stable user ID) | OIDC, MCP |
| `X-Auth-Groups` | Comma-separated group memberships | OIDC (if `groups` scope) |
| `X-Auth-Token` | The validated access token | All modes |
These headers are trustworthy because the auto-generated `NetworkPolicy` restricts pod ingress to the sidecar port only — external traffic cannot reach the application container directly, so headers cannot be spoofed.
Applications should read these headers to obtain authenticated user information (e.g. for display, authorisation decisions, or audit logging) instead of implementing their own authentication.
**See**: [Developer Guide - Accessing Authenticated User Information](DEVELOPER-GUIDE.md#accessing-authenticated-user-information) for code examples.
--- ---
@@ -1398,14 +1848,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
``` ```
--- ---
@@ -1469,6 +1927,88 @@ 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. **Secrets overlay**: `secrets/overlays/oci-dev/kustomization.yaml` — references `../../base`, add cloud-specific SealedSecrets if needed
6. **Secrets patch**: Add patch to `infra/overlays/oci-dev/kustomization.yaml` to swap secrets path to `secrets/overlays/oci-dev`
7. **Bootstrap**: `./bootstrap.sh oci-dev`
---
## Glossary ## Glossary
### Terms ### Terms
@@ -1601,6 +2141,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

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cert-manager-application.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- cluster-resources-application.yaml

View File

@@ -0,0 +1,42 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: databunker
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
labels:
app.kubernetes.io/name: databunker
app.kubernetes.io/part-of: identity
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://securitybunker.github.io/databunkerpro-setup
chart: databunkerpro
targetRevision: "0.1.0"
helm:
releaseName: databunkerpro
valueFiles:
- $values/infra/values/base/databunker-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: databunker
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- databunker.yaml

View File

@@ -18,7 +18,7 @@ spec:
source: source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: apps path: apps/overlays/upc-dev
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: apps namespace: apps

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- enterprise-apps.yaml

View File

@@ -21,7 +21,7 @@ spec:
helm: helm:
releaseName: fluent-bit releaseName: fluent-bit
valueFiles: valueFiles:
- $values/infra/values/fluent-bit-values.yaml - $values/infra/values/base/fluent-bit-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

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- fluent-bit.yaml

View File

@@ -21,7 +21,7 @@ spec:
helm: helm:
releaseName: gitea-actions releaseName: gitea-actions
valueFiles: valueFiles:
- $values/infra/values/gitea-actions-values.yaml - $values/infra/values/base/gitea-actions-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

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gitea-actions.yaml

View File

@@ -21,7 +21,8 @@ spec:
helm: helm:
releaseName: gitea releaseName: gitea
valueFiles: valueFiles:
- $values/infra/values/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

@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gitea.yaml
- gitea-backup-s3-sealed.yaml
- gitea-credentials-sealed.yaml
- gitea-runner-token-sealed.yaml
- gitea-smtp-secret-sealed.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- grafana-dashboards.yaml

View File

@@ -21,7 +21,8 @@ spec:
helm: helm:
releaseName: grafana releaseName: grafana
valueFiles: valueFiles:
- $values/infra/values/grafana-values.yaml - $values/infra/values/base/grafana-values.yaml
- $values/infra/values/upc-dev/grafana-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

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- grafana.yaml

View File

@@ -0,0 +1,21 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: homepage-services-reader
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: homepage-services-reader
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: homepage-services-reader
subjects:
- kind: ServiceAccount
name: homepage
namespace: homepage

View File

@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: homepage-widget-credentials
namespace: homepage
spec:
encryptedData:
HOMEPAGE_VAR_GITEA_TOKEN: AgAVN1C931EQpn+sodr3CpjlhORfJVTW8aUr+pGZQb+65Pb8QLGeVGVa7Jv60gDJUX3r+93/jMrEbCOeDL6I4qCz/V35wMCxFZLnXIdkmto0W4MKt6cK8To1/OP7EhQJOGBlSuOFsrwoy+HDtvLIqmyF0nrxhTusm9/NHrw+gCVwSTPhiAX1MCuSOSRWpbXvyNphW8j7aqUaV6ixDt424Fe4alEIShYELcS3EX/VPgsf2p2bhvBRCQOh3LEprkuxSFMuPfCBk06TPTbIN4saNVm0Ke0zW/pxkVNSiIxEnKjOmpPJtacsfWN7du+nQbx276G2qvWrf+iawJVq0Z/SLikA/NUFBL6EjSRfgE3cSOri8sbxsd0AycsFGyp98EM29wE+WOQl52M/lwl02EmCivqkICSO7Jp9pM1ScbmRMa5vcnupsGbVDxhRKLqxhAskt/BXDkRzvHN31gH3YmelES3JuqNMHV0urFxmX2oOX9Pxbtv63csc+zhy1Ui5aoex7TPnLdk7kYLSAE2MSrzT6wHvVhBC5kNnDYVrLehvJrT+eNh0MOLx2wkuJmIOxRAGUyNi5DfDnP6qnvj2aefEymLuOXAIUXH8DbeBtrjsd74HX2hhIfBlPkXvhJR3ks7i5RXjK2/YYHkgJ+nJoW80S9N7ciaRy103g74TNJZt6QzzL5Vb80qZ6yQOD4G081KmTLDmhHjJVIIv9M3nLh2s0IeBV3/Z5qHZmtjN7sSaKAn4MIr5FaH9quhx
HOMEPAGE_VAR_GRAFANA_TOKEN: AgBloBlOlP+R/4VizE1CGpj0wyiwU14BemAnuUpld7OvOGc67dwfDPyponkQXjAZg3UU2cZ70A51WUAuVlAr+25Ktlf/FW2OBqj+1BJOCqMMyu+kv026yjX2aB8dKGzlTxgF8aji+j1mC8vP3vvmgI4Zf2HQAH7uFwLfeo8+QnV5EyhcExSS0xDne+VtOP9jNXbPRayry0DdyRVtaeKAiZacO+45oAJWszWOwmoMTg9FZQkLjER6Q0tyI6NnoNObsFCnh56chZTdzBOYtmPnwld1bP2FjoJDqn8AfRwbPTIj7t0eFP7WLUO7GQKpxVl+pFwJLb5xCOw2+HNtp1BhNCu7icuc0P88IlvwzkbN0lXJbYigVOzyjEo8f/al1DXPM4WaB/Nqmr7Mtt8KTRh2WMVTgiX5jsu25D0rGDvY9gqfBBqswkRhCLsG0v0EN32zXj1/52KYdmB7pk/+2lMwSaGMS11MOenHeU1Z95fGxm9f3EGF0E8xlFr4FowgsNwr+tJQqpM0bT/4mZnaQbGWtKPFizMtsfQFm+rHFcNCrGaOuecslmiIJs8lTm18KlrncsGfxNS64tVXk+LvydU0rwybvpg2rQjEWtAl1IQsaaiz96OAlYxxK1MGxN7KE6F8R4kfnWTZ5Fs1KMmd/DOIVBXyCbqXxk8pbekmaIeNSfv92JNZ0QNJWsBa2vgQ24WI2pb4XiR0BvtLpt3BVlZUcSK92SzUblWmYWVMwHYCJkEeEUV1PhYEmyiN+V/Kq5Qb
template:
metadata:
creationTimestamp: null
name: homepage-widget-credentials
namespace: homepage

View File

@@ -0,0 +1,43 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: homepage
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "3"
labels:
app.kubernetes.io/name: homepage
app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://jameswynn.github.io/helm-charts
chart: homepage
targetRevision: "2.1.0"
helm:
releaseName: homepage
valueFiles:
- $values/infra/values/base/homepage-values.yaml
- $values/infra/values/upc-dev/homepage-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: homepage
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- homepage.yaml
- homepage-widget-credentials-sealed.yaml
- homepage-extra-rbac.yaml

View File

@@ -0,0 +1,48 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: karpor
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
labels:
app.kubernetes.io/name: karpor
app.kubernetes.io/part-of: developer-portal
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://kusionstack.github.io/charts
chart: karpor
targetRevision: "0.7.6"
helm:
releaseName: karpor
valueFiles:
- $values/infra/values/base/karpor-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: karpor
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- karpor.yaml

View File

@@ -15,13 +15,14 @@ spec:
project: default project: default
sources: sources:
- repoURL: https://charts.bitnami.com/bitnami - repoURL: registry-1.docker.io/bitnamicharts
chart: keycloak chart: keycloak
targetRevision: "25.2.0" targetRevision: "25.2.0"
helm: helm:
releaseName: keycloak releaseName: keycloak
valueFiles: valueFiles:
- $values/infra/values/keycloak-values.yaml - $values/infra/values/base/keycloak-values.yaml
- $values/infra/values/upc-dev/keycloak-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
@@ -42,7 +43,7 @@ spec:
- ServerSideApply=true - ServerSideApply=true
ignoreDifferences: ignoreDifferences:
- group: batch - group: apps
kind: Job kind: StatefulSet
jsonPointers: jsonPointers:
- /spec/template/spec/containers/0/args - /spec/volumeClaimTemplates

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- keycloak.yaml
- keycloak-credentials-sealed.yaml

View File

@@ -0,0 +1,25 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- traefik-application
- keycloak
- grafana
- cert-manager-application
- kyverno
- sealedsecrets
- prometheus
- loki
- fluent-bit
- enterprise-apps
- cluster-resources-application
- kyverno-policies
- gitea
- gitea-actions
- opencost
- renovate
- tempo
- grafana-dashboards
- karpor
- databunker
- homepage
- vault

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- kyverno-policies.yaml

View File

@@ -27,7 +27,6 @@ spec:
automated: automated:
prune: true prune: true
selfHeal: true selfHeal: true
allowEmpty: false
syncOptions: syncOptions:
- CreateNamespace=true - CreateNamespace=true
- Validate=true - Validate=true

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- kyverno.yaml

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- loki.yaml

View File

@@ -21,7 +21,7 @@ spec:
helm: helm:
releaseName: loki releaseName: loki
valueFiles: valueFiles:
- $values/infra/values/loki-values.yaml - $values/infra/values/base/loki-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
@@ -40,3 +40,9 @@ spec:
- CreateNamespace=true - CreateNamespace=true
- Validate=true - Validate=true
- ServerSideApply=true - ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- opencost.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

@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- prometheus.yaml

View File

@@ -21,7 +21,7 @@ spec:
helm: helm:
releaseName: prometheus releaseName: prometheus
valueFiles: valueFiles:
- $values/infra/values/prometheus-values.yaml - $values/infra/values/base/prometheus-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

Some files were not shown because too many files have changed in this diff Show More