15 Commits

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

2
.gitattributes vendored
View File

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

View File

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

117
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 multi-cloud Kubernetes (UpCloud, AWS EKS, Azure AKS, GCP GKE) > **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for UpCloud Managed Kubernetes
[![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-Multi--Cloud-orange)]() [![Kubernetes](https://img.shields.io/badge/Kubernetes-UpCloud-orange)](https://upcloud.com/)
--- ---
@@ -57,11 +57,11 @@ 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, Vault, Vault Secrets Operator, Homepage (platform dashboard) - **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets
- **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
- **Secrets**: Vault Secrets Operator (VSO) syncs secrets from HashiCorp Vault to K8s - **Secrets**: Sealed Secrets for secure Git storage
### Key Features ### Key Features
@@ -84,51 +84,31 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
├── _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 (Kustomize multi-cluster) ├── infra/ # Infrastructure ArgoCD Applications (Kustomize multi-cluster)
│ ├── base/ # Base ArgoCD Application manifests (one dir per component) │ ├── base/ # Base ArgoCD Application manifests (EU defaults)
│ │ ├── kustomization.yaml # Aggregates all component subdirectories │ │ ├── kustomization.yaml
│ │ ├── traefik-application/ │ │ ├── traefik-application.yaml
│ │ ├── kustomization.yaml │ │ ├── keycloak.yaml
│ │ │ └── traefik-application.yaml │ │ ── grafana.yaml
│ │ ├── keycloak/ │ │ ├── gitea.yaml
│ │ │ ├── kustomization.yaml │ │ ├── gitea-actions.yaml
│ │ │ └── keycloak.yaml │ │ ├── tempo.yaml
│ │ ├── grafana/ │ │ ├── renovate.yaml
│ │ ├── prometheus/ │ │ ├── ... # All other Application manifests
│ │ ── ... # Each component in its own subdirectory │ │ ── secrets.yaml
│ └── secrets/ ├── overlays/ # Per-cluster overrides
│ ├── overlays/ # Per-cluster overrides (Kustomize) │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is)
│ │ ── upc-dev/ # UpCloud Dev — includes all base components │ │ ── upc-prod/ # UpCloud Prod cluster (patches value paths)
│ │ ├── 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 │ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides │ └── values/ # Helm value overrides
│ ├── base/ # Shared cloud-agnostic values │ ├── base/ # Shared values (all clusters)
│ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing) │ ├── upc-dev/ # UpCloud Dev-specific values
── upc-prod/ # UpCloud Prod ── upc-prod/ # UpCloud Prod-specific values
│ ├── 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 (Kustomize, same pattern as infra) ├── apps/ # Business Applications
│ ├── base/ # One subdirectory per app │ ├── mcp10x.yaml
│ ├── kustomization.yaml │ ├── musicman.yaml
│ ├── musicman/ │ ├── dot-ai-stack.yaml
│ ├── mcp10x/ └── argo-mcp.yaml
│ │ ├── 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
@@ -187,7 +167,7 @@ Developer commits code → CI/CD builds image → Updates helm-prod-values → A
**Quick version**: **Quick version**:
1. Create `apps/myapp.yaml` (ArgoCD Application manifest) 1. Create `apps/myapp.yaml` (ArgoCD Application manifest)
2. Create `helm-prod-values/myapp/values.yaml` (configuration) 2. Create `helm-prod-values/myapp/values.yaml` (configuration)
3. Write secrets to Vault and create VaultStaticSecret CRD if needed 3. Create sealed secrets if needed
4. Commit and push - ArgoCD auto-syncs! 4. Commit and push - ArgoCD auto-syncs!
### Update an Existing Application ### Update an Existing Application
@@ -200,18 +180,22 @@ Developer commits code → CI/CD builds image → Updates helm-prod-values → A
### Manage Secrets ### Manage Secrets
**See detailed guide**: [Vault Secrets Operator Reference](docs/vault-secrets-operator.md) **See detailed guide**: [Developer Guide - Working with Secrets](docs/DEVELOPER-GUIDE.md#working-with-secrets)
```bash ```bash
# 1. Write secret to Vault # Create plain secret
vault kv put kv/myapp/myapp-creds KEY=value kubectl create secret generic myapp-creds \
--from-literal=KEY=value \
--dry-run=client -o yaml > private/myapp-creds.yaml
# 2. Create VaultStaticSecret CRD (one-time, commit to git) # Seal it
# See docs/vault-secrets-operator.md for CRD template kubeseal --format=yaml --cert=pub-cert.pem \
< private/myapp-creds.yaml > secrets/myapp-creds-sealed.yaml
# 3. Rotate secrets — no git commit needed! # Commit sealed version
vault kv put kv/myapp/myapp-creds KEY=new-value git add secrets/myapp-creds-sealed.yaml
# VSO picks up changes within 30 seconds git commit -m "Add myapp credentials"
git push
``` ```
### Enable Authentication ### Enable Authentication
@@ -324,7 +308,7 @@ kubectl patch application myapp -n argocd \
## 🔐 Security ## 🔐 Security
### Secret Management ### Secret Management
-Vault Secrets Operator (VSO) for secret management -Sealed Secrets for Git storage
- ✅ Kyverno auto-clones secrets to namespaces - ✅ Kyverno auto-clones secrets to namespaces
- ❌ Never commit plain secrets - ❌ Never commit plain secrets
@@ -351,8 +335,7 @@ kubectl patch application myapp -n argocd \
| **Traefik** | Ingress controller | `traefik` | 2 | | **Traefik** | Ingress controller | `traefik` | 2 |
| **Cert-Manager** | TLS certificates | `cert-manager` | 1 | | **Cert-Manager** | TLS certificates | `cert-manager` | 1 |
| **Kyverno** | Policy engine | `kyverno` | 1 | | **Kyverno** | Policy engine | `kyverno` | 1 |
| **Vault** | Secret storage | `vault` | 1 | | **Sealed Secrets** | Secret encryption | `kube-system` | 1 |
| **Vault Secrets Operator** | Secret sync (Vault → K8s) | `vault-secrets-operator-system` | 1 |
| **Prometheus** | Metrics | `monitoring` | 1 | | **Prometheus** | Metrics | `monitoring` | 1 |
| **Grafana** | Dashboards | `monitoring` | 1 | | **Grafana** | Dashboards | `monitoring` | 1 |
| **Loki** | Logs | `monitoring` | 1 | | **Loki** | Logs | `monitoring` | 1 |
@@ -360,6 +343,7 @@ 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)
@@ -377,7 +361,7 @@ kubectl patch application myapp -n argocd \
## 📖 Key Concepts ## 📖 Key Concepts
### App-of-Apps Pattern ### App-of-Apps Pattern
`_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`. `_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{upc-dev,upc-prod}/` render the base Applications with per-cluster patches (e.g., swapping value file paths from `upc-dev` to `upc-prod`).
### Multi-Source Pattern ### Multi-Source Pattern
Applications reference both: Applications reference both:
@@ -452,7 +436,7 @@ Applications deploy in order using `argocd.argoproj.io/sync-wave`:
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-prod-values/` 3. Create Helm values in `helm-prod-values/`
4. Write secrets to Vault and create VaultStaticSecret CRD if needed 4. Create sealed secrets if needed
5. Commit and push - ArgoCD handles the rest! 5. Commit and push - ArgoCD handles the rest!
### Modifying Infrastructure ### Modifying Infrastructure
@@ -474,14 +458,16 @@ Documentation lives in `docs/`. To update:
## 📝 Notes ## 📝 Notes
### Current Environment ### Current Environment
- **Provider**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) - **Provider**: UpCloud Managed Kubernetes
- **Active clusters**: UpCloud (upc-dev, upc-prod)
- **Environment**: Production (internal use only) - **Environment**: Production (internal use only)
- **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
- **Auth**: Disabled for ArgoCD (internal access) - **Auth**: Disabled for ArgoCD (internal access)
- **Backup**: Gitea daily backup to S3-compatible storage - **Backup**: None (cluster rebuildable via GitOps)
### Known Limitations ### Known Limitations
- No automated backups (yet)
- Secret rotation not automated - Secret rotation not automated
- Multi-cluster limited to upc-dev and upc-prod environments
- DNS management is manual - DNS management is manual
**Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery) **Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery)
@@ -496,8 +482,7 @@ Documentation lives in `docs/`. To update:
- [Traefik Documentation](https://doc.traefik.io/traefik/) - [Traefik Documentation](https://doc.traefik.io/traefik/)
- [Cert-Manager Documentation](https://cert-manager.io/docs/) - [Cert-Manager Documentation](https://cert-manager.io/docs/)
- [Grafana Tempo Documentation](https://grafana.com/docs/tempo/) - [Grafana Tempo Documentation](https://grafana.com/docs/tempo/)
- [Vault Secrets Operator](https://developer.hashicorp.com/vault/docs/platform/k8s/vso) - [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
- [HashiCorp Vault](https://developer.hashicorp.com/vault/docs)
### Related Repositories ### Related Repositories
- [forte-helm](https://git.forteapps.net/Forte/forte-helm) - Helm chart templates - [forte-helm](https://git.forteapps.net/Forte/forte-helm) - Helm chart templates
@@ -519,7 +504,7 @@ Internal use only. Not for public distribution.
--- ---
**Last Updated**: 2026-04-22 **Last Updated**: 2026-03-16
**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

@@ -1,32 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/aks-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/aks-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/eks-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/eks-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

@@ -1,32 +0,0 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/gke-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

View File

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

View File

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: argocd-mcp-credentials
namespace: argocd-mcp
spec:
type: kv-v2
mount: kv
path: argocd-mcp/argocd-mcp-credentials
destination:
name: argocd-mcp-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: auth-oidc
namespace: argocd-mcp
spec:
type: kv-v2
mount: kv
path: argocd-mcp/auth-oidc
destination:
name: auth-oidc
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,8 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- argo-mcp.yaml
- vault-auth.yaml
- auth-oidc-vault.yaml
- argocd-mcp-credentials-vault.yaml
# Removed: argocdmcp-auth-oidc-sealed.yaml, argocd-mcp-credentials.yaml (migrated to VSO)

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-argocd-mcp
namespace: argocd-mcp
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: argocd-mcp
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-argocd-mcp
serviceAccount: vault-auth-argocd-mcp
audiences:
- vault

View File

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

View File

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: dot-ai-secrets
namespace: dot-ai
spec:
type: kv-v2
mount: kv
path: dot-ai/dot-ai-secrets
destination:
name: dot-ai-secrets
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dot-ai-stack.yaml
- vault-auth.yaml
- dot-ai-secrets-vault.yaml
# Removed: dot-ai-secrets.yaml (migrated to VSO)

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-dot-ai
namespace: dot-ai
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: dot-ai
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-dot-ai
serviceAccount: vault-auth-dot-ai
audiences:
- vault

View File

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

View File

@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: app-credentials
namespace: mcp10x
spec:
type: kv-v2
mount: kv
path: mcp10x/app-credentials
destination:
name: app-credentials
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- mcp10x.yaml
- vault-auth.yaml
- app-credentials-vault.yaml
# Removed: forte10x-app-credentials-sealed.yaml (migrated to VSO)

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-mcp10x
namespace: mcp10x
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: mcp10x
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-mcp10x
serviceAccount: vault-auth-mcp10x
audiences:
- vault

View File

@@ -36,8 +36,13 @@ 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

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- musicman.yaml
- vault-auth.yaml
- musicman-credentials-vault.yaml
# Removed: musicman-credentials.yaml (migrated to VSO)

View File

@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: musicman-credentials
namespace: music-man
spec:
type: kv-v2
mount: kv
path: music-man/musicman-credentials
destination:
name: musicman-credentials
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-music-man
namespace: music-man
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: music-man
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-music-man
serviceAccount: vault-auth-music-man
audiences:
- vault

View File

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ts-mcp.yaml
- vault-auth.yaml
- ts-mcp-secrets-vault.yaml
# Removed: ts-mcp-secrets-sealed.yaml (migrated to VSO)

View File

@@ -1,13 +0,0 @@
---
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,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: ts-mcp-secrets
namespace: ts-mcp
spec:
type: kv-v2
mount: kv
path: ts-mcp/ts-mcp-secrets
destination:
name: ts-mcp-secrets
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-ts-mcp
namespace: ts-mcp
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: ts-mcp
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-ts-mcp
serviceAccount: vault-auth-ts-mcp
audiences:
- vault

View File

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

View File

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

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

View File

@@ -1,53 +0,0 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: feedback
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "12"
labels:
app.kubernetes.io/name: feedback
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/feedback/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: feedback
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
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

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

View File

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

View File

@@ -1,9 +1,8 @@
#!/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
CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod|aks-dev|aks-prod|eks-dev|eks-prod|gke-dev|gke-prod)}" CLUSTER="${1:?Usage: ./bootstrap.sh <cluster> (upc-dev|upc-prod)}"
echo "running $0 for cluster: ${CLUSTER}..." echo "running $0 for cluster: ${CLUSTER}..."
@@ -18,7 +17,7 @@ echo "Bootstrapping cluster: ${clusterName} (${CLUSTER})..."
Bootstrap() Bootstrap()
{ {
ArgoCd ArgoCd
Gitea # Gitea
} }
@@ -28,8 +27,8 @@ Bootstrap()
Gitea() Gitea()
{ {
echo "Installing secret..." echo "Installing secret..."
kubectl apply -f "private/${CLUSTER}/gitea-repo-main.yaml" kubectl apply -f private/gitea-repo-main.yaml
kubectl apply -f "private/${CLUSTER}/main.key" kubectl apply -f private/main.key
} }
############################################################ ############################################################
@@ -37,15 +36,10 @@ Gitea()
############################################################ ############################################################
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..."
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/base/argocd-values.yaml \ --values infra/values/base/argocd-values.yaml \
--values "infra/values/${CLUSTER}/argocd-values.yaml" \ --values "infra/values/${CLUSTER}/argocd-values.yaml" \
@@ -55,4 +49,4 @@ ArgoCd()
kubectl apply -f "_app-of-apps-${CLUSTER}.yaml" -n argocd kubectl apply -f "_app-of-apps-${CLUSTER}.yaml" -n argocd
} }
Bootstrap # Bootstrap

View File

@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: argocd-notifications-secret
namespace: argocd
spec:
type: kv-v2
mount: kv
path: argocd/argocd-notifications-secret
destination:
name: argocd-notifications-secret
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

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

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

@@ -1,16 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: forte-helm-repo
namespace: argocd
spec:
type: kv-v2
mount: kv
path: argocd/forte-helm-repo
destination:
name: forte-helm-repo
create: true
labels:
argocd.argoproj.io/secret-type: repository
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,17 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: forte10x-repo-creds
namespace: argocd
spec:
type: kv-v2
mount: kv
path: argocd/forte10x-repo-creds
destination:
name: forte10x-repo-creds
create: true
type: Opaque
labels:
argocd.argoproj.io/secret-type: repository
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

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

View File

@@ -1,17 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: mcp10x-repo-creds
namespace: argocd
spec:
type: kv-v2
mount: kv
path: argocd/mcp10x-repo-creds
destination:
name: mcp10x-repo-creds
create: true
type: Opaque
labels:
argocd.argoproj.io/secret-type: repository
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -245,12 +245,6 @@ spec:
secretKeyRef: secretKeyRef:
name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}" name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}"
key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}" key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}"
- name: AUTH_OIDC_IDP_HINT
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-idp-hint\" || '' }}"
- name: AUTH_OIDC_BROKER_ALIAS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-broker-alias\" || '' }}"
- name: AUTH_OIDC_BROKER_TOKEN_HEADER
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-broker-token-header\" || '' }}"
resources: resources:
limits: limits:
cpu: 50m cpu: 50m
@@ -330,8 +324,6 @@ spec:
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
- name: AUTH_MCP_SCOPES_SUPPORTED - name: AUTH_MCP_SCOPES_SUPPORTED
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}"
- name: AUTH_MCP_IDP_HINT
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-idp-hint\" || '' }}"
resources: resources:
limits: limits:
cpu: 50m cpu: 50m

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-argocd
namespace: argocd
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: argocd
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-argocd
serviceAccount: vault-auth-argocd
audiences:
- vault

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,32 +0,0 @@
{
"$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

@@ -60,16 +60,18 @@ If you do need cluster access, install:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
``` ```
2. **vault** CLI - For managing secrets in HashiCorp Vault 2. **kubeseal** - For sealing secrets
```bash ```bash
# macOS # macOS
brew install hashicorp/tap/vault brew install kubeseal
# Windows # Windows
choco install vault choco install kubeseal
# Linux # Linux
# See https://developer.hashicorp.com/vault/install wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/kubeseal-0.24.0-linux-amd64.tar.gz
tar -xvzf kubeseal-0.24.0-linux-amd64.tar.gz
sudo mv kubeseal /usr/local/bin/
``` ```
3. **Git** - Version control 3. **Git** - Version control
@@ -632,100 +634,125 @@ git push
### Understanding Secret Management ### Understanding Secret Management
Secrets are managed via **HashiCorp Vault** and synced to Kubernetes by the **Vault Secrets Operator (VSO)**. See [Vault Secrets Operator Reference](vault-secrets-operator.md) for full details. **NEVER commit plain secrets to Git.** We use **Sealed Secrets** to encrypt secrets before committing.
**NEVER commit plain secret values to Git.** Only VaultStaticSecret CRD manifests are committed.
### Creating a New Secret ### Creating a New Secret
#### Step 1: Write Secret to Vault #### Step 1: Create Plain Secret Locally
```bash ```bash
vault kv put kv/myapp/myapp-credentials \ cd ~/dev/k8s/launchpad
API_KEY=your-secret-key-here \
DB_PASSWORD=super-secret-password # Create secret in private/ folder (Git-ignored)
kubectl create secret generic myapp-credentials \
--from-literal=API_KEY=your-secret-key-here \
--from-literal=DB_PASSWORD=super-secret-password \
--dry-run=client -o yaml > private/myapp-credentials.yaml
``` ```
#### Step 2: Create VaultStaticSecret CRD **DO NOT commit this file!** It's in `private/` which is Git-ignored.
Create a YAML file (e.g., `apps/base/myapp/myapp-credentials-vault.yaml`): #### Step 2: Seal the Secret
```yaml Get the public certificate (one-time setup):
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-credentials
namespace: myapp
spec:
type: kv-v2
mount: kv
path: myapp/myapp-credentials
destination:
name: myapp-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth
```
#### Step 3: Add VaultAuth (if new namespace)
If this is a new namespace, also create a `vault-auth.yaml` with a ServiceAccount and VaultAuth CRD. See [VSO Reference](vault-secrets-operator.md#vaultauth) for template.
#### Step 4: Commit and Push
```bash ```bash
git add apps/base/myapp/myapp-credentials-vault.yaml # Fetch public cert from cluster
git commit -m "Add myapp credentials (VSO)" kubeseal --fetch-cert \
--controller-name=sealed-secrets-controller \
--controller-namespace=kube-system \
> pub-cert.pem
```
Seal your secret:
```bash
kubeseal --format=yaml \
--cert=pub-cert.pem \
< private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml
```
#### Step 3: Commit Sealed Secret
```bash
git add secrets/myapp-credentials-sealed.yaml
git commit -m "Add myapp credentials (sealed)"
git push git push
``` ```
ArgoCD syncs the CRD, VSO creates the K8s Secret. #### Step 4: Reference Secret in Application
#### Step 5: Reference Secret in Application
Update your `helm-prod-values/myapp/values.yaml`: Update your `helm-prod-values/myapp/values.yaml`:
```yaml ```yaml
app: app:
envSecretName: "myapp-credentials" # VSO creates this K8s Secret envSecretName: "myapp-credentials" # References the SealedSecret
``` ```
### Updating / Rotating a Secret Commit and push:
**No git commit needed** — just update in Vault:
```bash ```bash
vault kv put kv/myapp/myapp-credentials \ cd ~/dev/k8s/helm-prod-values
API_KEY=new-key-here \ git add myapp/values.yaml
DB_PASSWORD=new-password git commit -m "Reference myapp credentials"
git push
``` ```
VSO picks up changes within 30 seconds. Restart pods if they don't watch for secret updates: ### Updating a Secret
To update an existing secret:
```bash ```bash
# 1. Create new version of secret
kubectl create secret generic myapp-credentials \
--from-literal=API_KEY=new-key-here \
--from-literal=DB_PASSWORD=new-password \
--dry-run=client -o yaml > private/myapp-credentials.yaml
# 2. Seal it
kubeseal --format=yaml \
--cert=pub-cert.pem \
< private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml
# 3. Commit sealed version
git add secrets/myapp-credentials-sealed.yaml
git commit -m "Update myapp credentials"
git push
# 4. Restart pods to pick up new secret
kubectl rollout restart deployment myapp -n myapp kubectl rollout restart deployment myapp -n myapp
``` ```
### Secret Best Practices ### Secret Best Practices
- Write secrets to Vault via UI or CLI — never commit values to Git ✅ **DO**:
- Use meaningful secret names matching the KV path convention: `kv/{namespace}/{secret-name}` - Store secrets in `private/` folder locally
- Always seal secrets before committing
- Delete plain secrets after sealing
- Use meaningful secret names
- Document what each secret contains - Document what each secret contains
- Use Vault's versioning for audit trail
❌ **DON'T**:
- Commit plain secrets to Git
- Share secrets via Slack/email
- Hard-code secrets in code
- Use the same secret across multiple environments
- Store secrets in Docker images
### Where Secrets Are Stored ### Where Secrets Are Stored
``` ```
┌────────────────────────────────────────────────────────────────── ┌─────────────────────────────────────────────────────────────┐
│ Location │ Content │ In Git? │ Location │ Content │ Committed?
├────────────────────────────┼─────────────────────────┼──────────┤ ├──────────────────────────────────────────────┼────────────
Vault KV (kv/{ns}/{name}) │ Secret values │ ❌ NO │ private/ │ Plain secrets │ ❌ NO
VaultStaticSecret CRD Sync config (no values)│ ✅ YES │ secrets/ │ Sealed secrets │ ✅ YES
│ Kubernetes cluster K8s Secret (synced) │ N/A │ │ Kubernetes cluster │ Unsealed secrets │ N/A
└────────────────────────────────────────────────────────────────── └─────────────────────────────────────────────────────────────┘
``` ```
**Vault Secrets Operator** syncs secrets from Vault to K8s automatically (30s refresh). **Sealed Secrets Controller** in the cluster decrypts sealed secrets automatically.
--- ---
@@ -859,13 +886,28 @@ In your identity provider (e.g., Keycloak):
#### Step 2: Create OIDC Secret #### Step 2: Create OIDC Secret
```bash ```bash
# Write OIDC secret to Vault # Create plain secret
vault kv put kv/myapp/auth-oidc \ kubectl create secret generic auth-oidc \
client-secret=your-oidc-client-secret \ --from-literal=client-secret=your-oidc-client-secret \
cookie-secret=$(openssl rand -hex 32) --from-literal=cookie-secret=$(openssl rand -hex 32) \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-auth-oidc.yaml
# Create VaultStaticSecret CRD (see docs/vault-secrets-operator.md for template) # Seal it
# Add to apps/base/myapp/auth-oidc-vault.yaml and commit kubeseal --format=yaml \
--cert=pub-cert.pem \
--namespace=myapp \
< private/myapp-auth-oidc.yaml \
> secrets/myapp-auth-oidc-sealed.yaml
# Commit sealed secret
cd ~/dev/k8s/launchpad
git add secrets/myapp-auth-oidc-sealed.yaml
git commit -m "Add OIDC secrets for myapp"
git push
# Clean up
rm private/myapp-auth-oidc.yaml
``` ```
#### Step 3: Configure Helm Values #### Step 3: Configure Helm Values
@@ -920,46 +962,6 @@ 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
@@ -1095,13 +1097,16 @@ ingress:
host: web-app.forteapps.net host: web-app.forteapps.net
``` ```
**With Vault OIDC secret**: **With sealed OIDC secret**:
```bash ```bash
# Write OIDC secret to Vault # Create and seal secret
vault kv put kv/web-app/auth-oidc \ kubectl create secret generic auth-oidc \
client-secret=super-secret-value \ --from-literal=client-secret=super-secret-value \
cookie-secret=$(openssl rand -hex 32) --from-literal=cookie-secret=$(openssl rand -hex 32) \
# Then create VaultStaticSecret CRD — see docs/vault-secrets-operator.md --namespace=web-app \
--dry-run=client -o yaml | \
kubeseal --format=yaml --cert=pub-cert.pem --namespace=web-app \
> secrets/web-app-auth-oidc-sealed.yaml
``` ```
#### Example 3: MCP Server with OAuth 2.0 #### Example 3: MCP Server with OAuth 2.0
@@ -1229,7 +1234,7 @@ kubectl logs -n myapp <pod-name> -c authn
- Use token auth for service-to-service communication - Use token auth for service-to-service communication
- Rotate tokens and secrets regularly - Rotate tokens and secrets regularly
- Use strong random tokens (32+ bytes) - Use strong random tokens (32+ bytes)
- Store client secrets in Vault - Store client secrets in SealedSecrets
- Test authentication before deploying to production - Test authentication before deploying to production
- Document which tokens/users have access - Document which tokens/users have access
@@ -1533,22 +1538,22 @@ curl http://localhost:8080
#### Problem: Secret not found #### Problem: Secret not found
**Check VSO sync status:** **Check if SealedSecret exists:**
```bash ```bash
kubectl get vaultstaticsecret -n myapp kubectl get sealedsecret -n myapp
kubectl get secret -n myapp kubectl get secret -n myapp
``` ```
**Solutions:** **Solutions:**
```bash ```bash
# Check VaultAuth is authenticated # Check if secret is in Git
kubectl get vaultauth -n myapp ls -l secrets/myapp-credentials-sealed.yaml
# Check VaultStaticSecret events # Re-apply sealed secret
kubectl describe vaultstaticsecret myapp-credentials -n myapp kubectl apply -f secrets/myapp-credentials-sealed.yaml
# Verify secret exists in Vault # Check sealed-secrets-controller logs
vault kv get kv/myapp/myapp-credentials kubectl logs -n kube-system deployment/sealed-secrets-controller
``` ```
#### Problem: Secret exists but pods can't access it #### Problem: Secret exists but pods can't access it
@@ -1659,7 +1664,7 @@ If you're stuck:
### Secret Management ### Secret Management
✅ **DO**: ✅ **DO**:
- Use Vault for all secrets (see docs/vault-secrets-operator.md) - Use kubeseal for all secrets
- Store plain secrets in password manager - Store plain secrets in password manager
- Rotate secrets regularly - Rotate secrets regularly
- Use different secrets per environment - Use different secrets per environment
@@ -1711,9 +1716,16 @@ kubectl rollout restart deployment myapp -n myapp
# Port-forward to service # Port-forward to service
kubectl port-forward -n myapp service/myapp 8080:3000 kubectl port-forward -n myapp service/myapp 8080:3000
# Write secret to Vault # Create secret
vault kv put kv/myapp/myapp-credentials KEY=value kubectl create secret generic myapp-credentials \
# Create VaultStaticSecret CRD — see docs/vault-secrets-operator.md --from-literal=KEY=value \
--dry-run=client -o yaml > private/myapp-credentials.yaml
# Seal secret
kubeseal --format=yaml \
--cert=pub-cert.pem \
< private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml
``` ```
### Repository Locations ### Repository Locations

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 setup is **cloud-agnostic**, with ready-to-use configurations for **UpCloud**, **AWS EKS**, **Azure AKS**, and **GCP GKE**. 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.
### Key Characteristics ### Key Characteristics
- **Environment**: Production (internal use only) - **Environment**: Production (internal use only)
- **Cluster Type**: Multi-cloud, multi-cluster via Kustomize overlays (UpCloud, AWS, Azure, GCP) - **Cluster Type**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
- **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)
@@ -63,7 +63,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Kubernetes Clusters │ │ Kubernetes Clusters │
│ (UpCloud, AWS, Azure, GCP) │ (UpCloud: upc-dev, upc-prod)
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ ArgoCD │ │ │ │ ArgoCD │ │
@@ -120,47 +120,46 @@ launchpad/
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod cluster) ├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod cluster)
├── infra/ # Infrastructure ArgoCD Applications (Kustomize) ├── infra/ # Infrastructure ArgoCD Applications (Kustomize)
│ ├── base/ # Base Application manifests (one dir per component) │ ├── base/ # Base Application manifests (upc-dev defaults)
│ │ ├── kustomization.yaml # Aggregates all component subdirectories │ │ ├── kustomization.yaml
│ │ ├── traefik-application/ │ │ ├── traefik-application.yaml
│ │ ├── kustomization.yaml │ │ ├── keycloak.yaml
│ │ │ └── traefik-application.yaml │ │ ── grafana.yaml
│ │ ├── keycloak/ │ │ ├── gitea.yaml
│ │ │ ├── kustomization.yaml │ │ ├── gitea-actions.yaml
│ │ │ └── keycloak.yaml │ │ ├── tempo.yaml
│ │ ├── grafana/ │ │ ├── renovate.yaml
│ │ ├── prometheus/ │ │ ├── ... # All other Application manifests
│ │ ── ... # Each component in its own subdirectory │ │ ── secrets.yaml
│ └── secrets/ ├── overlays/ # Per-cluster overrides
│ ├── overlays/ # Per-cluster Kustomize overrides │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
│ │ ── upc-dev/ # UpCloud Dev — includes all (resources: ../../base) │ │ ── upc-prod/ # UpCloud Prod (patches value paths)
│ │ ├── 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 │ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides for infra │ └── values/ # Helm value overrides for infra
│ ├── base/ # Cloud-agnostic shared values │ ├── base/ # Shared values (all clusters)
├── upc-{dev,prod}/ # UpCloud: storage class, LB, pricing │ ├── traefik-values.yaml
├── aws-{dev,prod}/ # AWS: gp3, NLB, CUR pricing │ ├── keycloak-values.yaml
├── aks-{dev,prod}/ # Azure: managed-csi-premium, Standard LB │ ├── grafana-values.yaml
└── gcp-{dev,prod}/ # GCP: premium-rwo, L4 LB │ ├── prometheus-values.yaml
│ │ ├── gitea-values.yaml
│ │ └── ...
│ ├── upc-dev/ # upc-dev cluster-specific values
│ │ ├── traefik-values.yaml
│ │ ├── keycloak-values.yaml
│ │ └── grafana-values.yaml
│ └── upc-prod/ # upc-prod cluster-specific values
│ ├── traefik-values.yaml
│ ├── keycloak-values.yaml
│ └── grafana-values.yaml
├── apps/ # Business Application ArgoCD manifests (Kustomize) ├── apps/ # Business Application ArgoCD manifests (Kustomize)
│ ├── base/ # One subdirectory per app │ ├── base/ # Base app manifests
│ │ ├── kustomization.yaml │ │ ├── kustomization.yaml
│ │ ├── musicman/ │ │ ├── dot-ai-stack.yaml
│ │ ── mcp10x/ │ │ ── ...
│ │ ├── dot-ai-stack/
│ │ ├── ts-mcp/
│ │ └── argo-mcp/
│ └── overlays/ │ └── overlays/
│ ├── upc-dev/ # All apps (resources: ../../base) │ ├── upc-dev/ # Uses base as-is
── upc-prod/ # All apps + patches ── upc-prod/ # Patches value paths
│ └── aks-dev/ # Selective apps only
├── cluster-resources/ # Cluster-wide Kubernetes resources ├── cluster-resources/ # Cluster-wide Kubernetes resources
│ ├── ... │ ├── ...
@@ -176,8 +175,6 @@ launchpad/
**Key Points**: **Key Points**:
- `_app-of-apps-upc-dev.yaml` and `_app-of-apps-upc-prod.yaml` are the per-cluster root Applications - `_app-of-apps-upc-dev.yaml` and `_app-of-apps-upc-prod.yaml` are the per-cluster root Applications
- 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 - 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) - 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 - `apps/` follows the same base/overlays pattern for business applications
@@ -290,7 +287,7 @@ app-repository/
### The App-of-Apps Pattern ### The App-of-Apps Pattern
``` ```
_app-of-apps-{cluster}.yaml (Root, per cluster — e.g. upc-dev, eks-prod, gke-dev) _app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster)
├── infrastructure-apps (manages infra/) ├── infrastructure-apps (manages infra/)
│ ├── cluster-resources-application │ ├── cluster-resources-application
@@ -360,30 +357,16 @@ spec:
### Multi-Cluster Pattern ### Multi-Cluster Pattern
Kustomize overlays enable deploying the same Applications across clusters with different configurations. 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 ```yaml
# Option 1: Include ALL components (full cluster) # infra/base/ contains default (upc-dev) Applications
# infra/overlays/upc-dev/kustomization.yaml # Helm values are layered: base + cluster-specific
resources: valueFiles:
- ../../base # Pulls in every component subdirectory - $values/infra/values/base/traefik-values.yaml # Shared config
- $values/infra/values/upc-dev/traefik-values.yaml # Cluster-specific
# Option 2: Cherry-pick specific components (lightweight cluster) # infra/overlays/upc-prod/kustomization.yaml patches the second valueFile
# 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: patches:
- target: - target:
kind: Application kind: Application
@@ -394,15 +377,6 @@ patches:
value: $values/infra/values/upc-prod/traefik-values.yaml value: $values/infra/values/upc-prod/traefik-values.yaml
``` ```
Cloud-specific values (storage classes, load balancer annotations, cost model) are isolated in per-cluster value files. Base values are fully cloud-agnostic:
| Cloud | Storage Class | Load Balancer | OpenCost Provider |
|-------|--------------|---------------|-------------------|
| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud LB (ProxyProtocol v2) | Custom pricing |
| **AWS EKS** | `gp3` (EBS CSI) | NLB (ProxyProtocol v2) | AWS CUR |
| **Azure AKS** | `managed-csi-premium` | Standard LB (`externalTrafficPolicy: Local`) | Azure Billing API |
| **GCP GKE** | `premium-rwo` (PD CSI) | L4 passthrough NLB | GCP Cloud Billing |
**Benefits**: **Benefits**:
- Single source of truth for Application definitions - Single source of truth for Application definitions
- Cluster-specific values isolated per overlay - Cluster-specific values isolated per overlay
@@ -684,6 +658,6 @@ Notifications include:
--- ---
**Last Updated**: 2026-04-22 **Last Updated**: 2026-03-16
**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, AWS EKS, Azure AKS, GCP GKE, or any K8s cluster) 1. **Kubernetes cluster running** (UpCloud 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,13 +54,11 @@ kubectl get nodes
git clone https://git.forteapps.net/Forte/launchpad git clone https://git.forteapps.net/Forte/launchpad
cd launchpad cd launchpad
# 2. Run bootstrap script with cluster target # 2. Set cluster name (optional)
# Available clusters: upc-dev, upc-prod, eks-dev, eks-prod, export CLUSTER_NAME="prod-cluster-01"
# aks-dev, aks-prod, gke-dev, gke-prod
./bootstrap.sh upc-dev
# Cluster config is loaded from clusters/<cluster>.yaml # 3. Run bootstrap script
# (cloudProvider, trustedIPs, domain, etc.) ./bootstrap.sh
``` ```
**What Happens:** **What Happens:**
@@ -188,15 +186,13 @@ Save the following file in private/ (gitignored) folder as secret.yaml
<paste your private key here> <paste your private key here>
project: default project: default
``` ```
Write the secret to Vault: Seal the secret using `kubeseal` command
```bash ```bash
vault kv put kv/argocd/forte-helm-repo \ kubeseal --format=yaml \
type=git \ --namespace=argocd \
url=ssh://git@git.forteapps.net:2222/Forte/forte-helm.git \ < private/secret.yaml \
sshPrivateKey="$(cat private/ssh-key)" \ > secrets/forte-helm-repo-secret-sealed.yaml
project=default
``` ```
Then create a VaultStaticSecret CRD with `argocd.argoproj.io/secret-type: repository` label.
**Step 4: Register Repository in ArgoCD** **Step 4: Register Repository in ArgoCD**
@@ -501,7 +497,7 @@ See [Developer Guide](DEVELOPER-GUIDE.md#deploying-your-first-application) for d
**Quick checklist:** **Quick checklist:**
- [ ] Create `helm-prod-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
- [ ] Write secrets to Vault and create VaultStaticSecret CRD if needed - [ ] Create SealedSecret if needed
- [ ] Commit and push changes - [ ] Commit and push changes
- [ ] Verify sync in Slack/ArgoCD - [ ] Verify sync in Slack/ArgoCD
- [ ] Configure DNS for domain - [ ] Configure DNS for domain
@@ -672,61 +668,92 @@ db:
## Secret Management ## Secret Management
Secrets are managed via **HashiCorp Vault** and synced to Kubernetes by the **Vault Secrets Operator (VSO)**. See [Vault Secrets Operator Reference](vault-secrets-operator.md) for full details.
### Creating Secrets ### Creating Secrets
#### Step 1: Write to Vault #### Step 1: Get Public Certificate
```bash ```bash
# From literal values # Fetch sealed-secrets public cert (one-time)
vault kv put kv/myapp/myapp-credentials \ kubeseal --fetch-cert \
API_KEY=secret123 \ --controller-name=sealed-secrets-controller \
DB_PASSWORD=pass456 --controller-namespace=kube-system \
> pub-cert.pem
# Save this certificate for future use
``` ```
#### Step 2: Create VaultStaticSecret CRD #### Step 2: Create Plain Secret
```yaml
# apps/base/myapp/myapp-credentials-vault.yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-credentials
namespace: myapp
spec:
type: kv-v2
mount: kv
path: myapp/myapp-credentials
destination:
name: myapp-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth
```
#### Step 3: Commit CRD
```bash ```bash
git add apps/base/myapp/myapp-credentials-vault.yaml # Method 1: From literal values
git commit -m "Add myapp credentials (VSO)" kubectl create secret generic myapp-credentials \
--from-literal=API_KEY=secret123 \
--from-literal=DB_PASSWORD=pass456 \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-credentials.yaml
# Method 2: From file
kubectl create secret generic myapp-credentials \
--from-file=.env \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-credentials.yaml
# Method 3: From multiple files
kubectl create secret generic myapp-credentials \
--from-file=api-key.txt \
--from-file=db-password.txt \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-credentials.yaml
```
#### Step 3: Seal Secret
```bash
kubeseal --format=yaml \
--cert=pub-cert.pem \
--namespace=myapp \
< private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml
```
#### Step 4: Commit Sealed Secret
```bash
git add secrets/myapp-credentials-sealed.yaml
git commit -m "Add myapp credentials"
git push git push
# Delete plain secret
rm private/myapp-credentials.yaml
``` ```
ArgoCD syncs the CRD, VSO creates the K8s Secret automatically. ### Updating Secrets
### Updating / Rotating Secrets
**No git commit needed** — just update in Vault:
```bash ```bash
vault kv put kv/myapp/myapp-credentials \ # 1. Create new version
API_KEY=new-secret-key \ kubectl create secret generic myapp-credentials \
DB_PASSWORD=new-password --from-literal=API_KEY=new-secret-key \
--from-literal=DB_PASSWORD=new-password \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-credentials.yaml
# VSO picks up changes within 30 seconds # 2. Seal it
# Restart pods if needed kubeseal --format=yaml \
--cert=pub-cert.pem \
--namespace=myapp \
< private/myapp-credentials.yaml \
> secrets/myapp-credentials-sealed.yaml
# 3. Commit
git add secrets/myapp-credentials-sealed.yaml
git commit -m "Update myapp credentials"
git push
# 4. Restart pods to pick up new secret
kubectl rollout restart deployment myapp -n myapp kubectl rollout restart deployment myapp -n myapp
# 5. Delete plain secret
rm private/myapp-credentials.yaml
``` ```
### Viewing Secrets (Unsealed) ### Viewing Secrets (Unsealed)
@@ -803,13 +830,30 @@ OIDC auth requires an `auth-oidc` Secret with two keys:
CLIENT_SECRET="your-oidc-client-secret-from-provider" CLIENT_SECRET="your-oidc-client-secret-from-provider"
COOKIE_SECRET=$(openssl rand -hex 32) COOKIE_SECRET=$(openssl rand -hex 32)
# Write to Vault # Create plain secret
vault kv put kv/myapp/auth-oidc \ kubectl create secret generic auth-oidc \
client-secret=$CLIENT_SECRET \ --from-literal=client-secret=$CLIENT_SECRET \
cookie-secret=$COOKIE_SECRET --from-literal=cookie-secret=$COOKIE_SECRET \
--namespace=myapp \
--dry-run=client -o yaml > private/myapp-auth-oidc.yaml
# Create VaultStaticSecret CRD (one-time) and commit # Seal it
# See docs/vault-secrets-operator.md for CRD template kubeseal --format=yaml \
--cert=pub-cert.pem \
--namespace=myapp \
< private/myapp-auth-oidc.yaml \
> secrets/myapp-auth-oidc-sealed.yaml
# Apply sealed secret
kubectl apply -f secrets/myapp-auth-oidc-sealed.yaml
# Commit to Git
git add secrets/myapp-auth-oidc-sealed.yaml
git commit -m "Add OIDC secrets for myapp"
git push
# Clean up
rm private/myapp-auth-oidc.yaml
``` ```
#### Rotating Authentication Secrets #### Rotating Authentication Secrets
@@ -836,12 +880,16 @@ kubectl rollout restart deployment myapp -n myapp
# Rotate cookie secret (safe - invalidates existing sessions) # Rotate cookie secret (safe - invalidates existing sessions)
NEW_COOKIE_SECRET=$(openssl rand -hex 32) NEW_COOKIE_SECRET=$(openssl rand -hex 32)
# Update in Vault — no git commit needed # Recreate secret
vault kv put kv/myapp/auth-oidc \ kubectl create secret generic auth-oidc \
client-secret=$CLIENT_SECRET \ --from-literal=client-secret=$CLIENT_SECRET \
cookie-secret=$NEW_COOKIE_SECRET --from-literal=cookie-secret=$NEW_COOKIE_SECRET \
--namespace=myapp \
--dry-run=client -o yaml | \
kubeseal --format=yaml --cert=pub-cert.pem --namespace=myapp | \
kubectl apply -f -
# VSO picks up within 30s. Restart pods to use new secret: # Restart to pick up new secret
kubectl rollout restart deployment myapp -n myapp kubectl rollout restart deployment myapp -n myapp
``` ```
@@ -1214,21 +1262,13 @@ spec:
### Backup Strategy ### Backup Strategy
**Current State**: Gitea daily backups to S3-compatible storage **Current State**: No automated backups
**What Is Backed Up**: **What Needs Backup**:
- ✅ Gitea repositories + database: Daily CronJob (`cluster-resources/gitea-backup-cronjob.yaml`) uploads to S3-compatible storage with 7-day retention - ❌ Cluster state (not backed up - recreate via GitOps)
- ✅ Git repositories: Full cluster config recoverable from Git - ❌ Persistent volumes (currently not critical)
- ⚠️ Secrets: Sealed secrets in Git; unseal keys need safekeeping - ✅ Git repositories (Gitea provides backup)
- ⚠️ 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
@@ -1292,11 +1332,13 @@ kubectl get applications -n argocd -w
- pg_dump -U $DB_USER -d $DB_NAME > /backup/dump-$(date +%Y%m%d).sql - pg_dump -U $DB_USER -d $DB_NAME > /backup/dump-$(date +%Y%m%d).sql
``` ```
3. **Vault backup** 3. **Sealed Secrets private key backup**
```bash ```bash
# Vault data is stored on PVC — ensure PVC snapshots are configured # Backup sealed-secrets controller private key
# For disaster recovery, maintain Vault unseal keys in a secure location kubectl get secret -n kube-system sealed-secrets-key \
# All secrets can be re-seeded from source if needed -o yaml > sealed-secrets-key-backup.yaml
# Store in secure location (password manager, vault)
``` ```
--- ---
@@ -1328,9 +1370,6 @@ 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
@@ -1468,35 +1507,18 @@ git push
### Multi-Cluster Setup ### Multi-Cluster Setup
The repository supports multiple clusters across multiple clouds via Kustomize overlays: The repository supports multiple clusters via Kustomize overlays:
**Active clusters:**
- **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is - **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is
- **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod` - **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod`
**Cloud-ready templates (fill in `clusters/*.yaml` before use):**
- **eks-dev** / **eks-prod**: AWS EKS with NLB, gp3 storage, AWS CUR pricing
- **aks-dev** / **aks-prod**: Azure AKS with Standard LB, managed-csi-premium storage
- **gke-dev** / **gke-prod**: GCP GKE with L4 LB, premium-rwo storage
Each cluster has its own: Each cluster has its own:
- Root app-of-apps: `_app-of-apps-{cluster}.yaml` - Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml`
- Cluster config: `clusters/{cluster}.yaml` (domain, trustedIPs, cloudProvider) - Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/`
- Kustomize overlay: `infra/overlays/{cluster}/kustomization.yaml` - Sealed secrets: `secrets/upc-dev/` (others as needed)
- Helm value overrides: `infra/values/{cluster}/` (traefik, gitea, opencost) - Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/`
- Sealed secrets: `secrets/{cluster}/` (as needed)
- Apps overlay: `apps/overlays/{cluster}/`
Cloud-specific values handled per-cluster: To add a new cluster, create a new overlay directory (e.g., `infra/overlays/upc-staging/`) with patches that swap the value file paths.
| 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
@@ -1616,7 +1638,7 @@ echo "Remember to delete: $SECRET_FILE"
- [ ] Gitea 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 written to Vault and VaultStaticSecret CRD created - [ ] Secrets created and sealed
- [ ] DNS record added for domain - [ ] DNS record added for domain
- [ ] Application synced successfully - [ ] Application synced successfully
- [ ] Health check passed - [ ] Health check passed
@@ -1639,6 +1661,6 @@ echo "Remember to delete: $SECRET_FILE"
--- ---
**Last Updated**: 2026-04-22 **Last Updated**: 2026-03-16
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Emergency Contact**: #platform-support on Slack **Emergency Contact**: #platform-support on Slack

View File

@@ -180,7 +180,7 @@ Reference for:
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ Kubernetes Clusters (UpCloud, AWS, Azure, GCP) │ Kubernetes Clusters (UpCloud: upc-dev, upc-prod)
│ ┌──────────────────────────────────────────────────────┐ │ │ ┌──────────────────────────────────────────────────────┐ │
│ │ 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**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) - **Kubernetes**: UpCloud Managed Kubernetes (multi-cluster: upc-dev, upc-prod)
- **Ingress**: Traefik v2 - **Ingress**: Traefik v2
- **Certificates**: Cert-Manager + Let's Encrypt - **Certificates**: Cert-Manager + Let's Encrypt
- **Policies**: Kyverno - **Policies**: Kyverno
@@ -299,16 +299,11 @@ docs/
## 🔄 Documentation Versions ## 🔄 Documentation Versions
**Current Version**: 1.0.0 **Current Version**: 1.0.0
**Last Updated**: 2026-04-22 **Last Updated**: 2026-03-16
**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,7 +9,6 @@
- [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)
--- ---
@@ -20,10 +19,9 @@
| Component | Value | | Component | Value |
|-----------|-------| |-----------|-------|
| **Provider** | Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) | | **Provider** | UpCloud Managed Kubernetes |
| **Environment** | Dev + Production per cloud | | **Environment** | Production (internal use) |
| **Active clusters** | UpCloud (upc-dev, upc-prod) | | **Cluster Count** | Multi-cluster (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 |
@@ -44,7 +42,7 @@ Internet
[DNS: *.forteapps.net] [DNS: *.forteapps.net]
[Cloud Load Balancer] [UpCloud LoadBalancer]
[Traefik Ingress Controller] [Traefik Ingress Controller]
@@ -76,59 +74,40 @@ launchpad/
├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev) ├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev)
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod) ├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod)
├── infra/ # Infrastructure applications (Kustomize) ├── infra/ # Infrastructure applications
│ ├── base/ # One subdirectory per component │ ├── cluster-resources-application.yaml
│ ├── kustomization.yaml # Aggregates all component subdirectories │ ├── enterprise-apps.yaml
│ ├── traefik-application/ │ ├── traefik-application.yaml
│ │ ├── kustomization.yaml ├── cert-manager-application.yaml
│ │ └── traefik-application.yaml ├── kyverno.yaml
│ ├── keycloak/ │ ├── kyverno-policies.yaml
│ │ ├── kustomization.yaml ├── prometheus.yaml
│ │ └── keycloak.yaml ├── grafana.yaml
│ ├── grafana/ │ ├── loki.yaml
│ ├── prometheus/ │ ├── tempo.yaml
│ ├── loki/ │ ├── fluent-bit.yaml
│ ├── tempo/ │ ├── trivy.yaml
│ ├── gitea/ │ ├── gitea.yaml
│ ├── opencost/ │ ├── gitea-actions.yaml
│ ├── ... # Each component in own directory │ ├── sealedsecrets.yaml
│ └── secrets/ ── secrets.yaml
│ ├── overlays/ # Per-cluster: include all or cherry-pick │ ├── renovate.yaml
│ │ ├── upc-dev/ # resources: [../../base] (all components)
│ │ ├── upc-prod/ # resources: [../../base] + patches
│ │ ├── aks-dev/ # resources: [../../base/grafana, ...] (selective)
│ │ └── .../ # 8 clusters total
│ └── values/ │ └── values/
│ ├── base/ # Cloud-agnostic Helm values │ ├── argocd-values.yaml
│ ├── gitea-values.yaml ├── prometheus-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
└── opencost-values.yaml ├── gitea-actions-values.yaml
│ ├── fluent-bit-values.yaml
│ └── renovate-values.yaml
├── apps/ # Business applications (Kustomize) ├── apps/ # Business applications
│ ├── base/ # One subdirectory per app │ ├── mcp10x.yaml
│ ├── kustomization.yaml │ ├── musicman.yaml
│ ├── musicman/ │ ├── dot-ai-stack.yaml
│ ├── mcp10x/ └── argo-mcp.yaml
│ │ ├── 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
@@ -149,39 +128,12 @@ launchpad/
│ └── auth-sidecar-injector.yaml │ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed) ├── secrets/ # Application secrets (sealed)
│ ├── base/ # All SealedSecrets (shared across clouds) │ ├── argocd-mcp-credentials.yaml
│ ├── kustomization.yaml │ ├── dot-ai-secrets.yaml
│ ├── argocd-forte-helm-secret-sealed.yaml │ ├── gitea-credentials-sealed.yaml
│ ├── argocd-mcp-credentials.yaml │ ├── gitea-runner-token-sealed.yaml
│ ├── argocdmcp-auth-oidc-sealed.yaml │ ├── mcp10x-credentials-sealed.yaml
│ ├── dot-ai-secrets.yaml └── musicman-credentials.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
@@ -650,134 +602,10 @@ 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`
@@ -849,10 +677,6 @@ 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 \
@@ -893,15 +717,6 @@ 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`
@@ -1254,33 +1069,6 @@ kubectl get secret keycloak-client-<app> -n keycloak -o jsonpath='{.metadata.ann
**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`)
@@ -1384,46 +1172,6 @@ spec:
- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-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 - `synchronize: true` — changes to the source Secret are reflected in the clone
### Keycloak Microsoft/Entra Identity Provider
**File**: `infra/values/upc-dev/keycloak-values.yaml`
**Namespace**: `keycloak`
**Purpose**: Configures Microsoft Entra (Azure AD) as an external identity provider for the Forte realm, enabling SSO via Microsoft accounts with token storage for downstream API access (e.g., Microsoft Graph).
**Configuration via keycloakConfigCli**:
- IdP alias: `forte-entra`, provider: `microsoft`
- Client secret injected from `microsoft-idp-credentials` Secret via `$(env:MS_IDP_CLIENT_SECRET)` syntax
- `extraEnvVarsSecret: microsoft-idp-credentials` makes the Secret available as env vars to config-cli
**Key Configuration Notes**:
| Field | Location | Notes |
|-------|----------|-------|
| `tenant` | `config.tenant` | **Must be `tenant`, NOT `tenantId`** — wrong key silently falls back to `common` (multi-tenant) |
| `storeToken` | Top-level IdP field | **NOT inside `config`** — enables broker token storage for KC broker API |
| `defaultScope` | `config.defaultScope` | Space-separated: `openid email profile User.Read Mail.Send` |
| `syncMode` | `config.syncMode` | `IMPORT` — imports user on first login |
**Token Storage & Broker Access**:
- `storeToken: true` persists the Entra access token in Keycloak
- Realm role `default-roles-forte` includes composite `broker.read-token` — grants all realm users access to broker token API
- Broker token retrievable via: `GET /realms/forte/broker/forte-entra/token`
**Identity Provider Mappers**:
- `forte-entra-email`: Hardcodes `emailVerified=true` for Entra-authenticated users (Entra guarantees email verification)
**Required Secret** (`microsoft-idp-credentials`):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: microsoft-idp-credentials
namespace: keycloak
stringData:
MS_IDP_CLIENT_SECRET: "<entra-app-client-secret>"
```
### Default Namespace Blocker ### Default Namespace Blocker
**File**: `cluster-resources/policies/default-ns-blocker.yaml` **File**: `cluster-resources/policies/default-ns-blocker.yaml`
@@ -1768,23 +1516,7 @@ Forward to Application (localhost:3000)
Application processes request Application processes request
``` ```
#### Forwarded Headers **See**: [Developer Guide - Enabling Authentication](DEVELOPER-GUIDE.md#enabling-authentication-for-applications) for usage examples.
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.
--- ---
@@ -1818,22 +1550,14 @@ Recommended resource allocation:
### Storage Classes ### Storage Classes
Storage classes are cloud-specific and configured in per-cluster value overrides (`infra/values/{cluster}/gitea-values.yaml`): Default storage class used: **UpCloud default** (varies by provider)
| 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
``` ```
--- ---
@@ -1897,88 +1621,6 @@ POST /loki/api/v1/push
--- ---
## Cloud Overlay Pattern
### Overview
Cloud-specific configuration (StorageClass, LoadBalancer annotations, pricing models, etc.) lives in per-cloud overlay value files, **not** in `base/`. Adding a new cloud provider only requires a new overlay directory — no base changes.
### Supported Clouds
| Cloud | Dev overlay | Prod overlay | StorageClass | LB type |
|-------|-----------|-------------|-------------|---------|
| **UpCloud** | `upc-dev` | `upc-prod` | `upcloud-block-storage-maxiops` | UpCloud LB (proxy protocol v2) |
| **Azure AKS** | `aks-dev` | `aks-prod` | `managed-csi-premium` | Azure LB |
| **AWS EKS** | `eks-dev` | `eks-prod` | `gp3` | AWS NLB (proxy protocol) |
| **GCP GKE** | `gke-dev` | `gke-prod` | `premium-rwo` | GCP NEG |
Bootstrap any cluster with: `./bootstrap.sh <cluster>` (e.g., `./bootstrap.sh aks-dev`)
### How It Works
Each ArgoCD Application uses **multi-source Helm values** with two value files:
```yaml
# infra/base/gitea.yaml (example)
helm:
valueFiles:
- $values/infra/values/base/gitea-values.yaml # [0] cloud-agnostic
- $values/infra/values/upc-dev/gitea-values.yaml # [1] cloud-specific (default: upc-dev)
```
The `upc-prod` Kustomize overlay patches index `[1]` to swap the cloud-specific file:
```yaml
# infra/overlays/upc-prod/kustomization.yaml
- target:
kind: Application
name: gitea
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/gitea-values.yaml
```
### Components Using Cloud Overlays
| Component | Cloud-specific config | Overlay value file |
|-----------|----------------------|-------------------|
| **Traefik** | LB annotations, proxy protocol IPs | `traefik-values.yaml` |
| **Keycloak** | Hostname, TLS settings | `keycloak-values.yaml` |
| **Grafana** | Hostname, datasource URLs | `grafana-values.yaml` |
| **Gitea** | StorageClass (persistence + PostgreSQL) | `gitea-values.yaml` |
| **OpenCost** | Custom pricing model (CPU/RAM/storage rates) | `opencost-values.yaml` |
### Backup CronJob
The `gitea-backup` CronJob uses a generic `s3` alias for `minio/mc`. The actual endpoint and credentials come from the `gitea-backup-s3` Sealed Secret, which is per-cloud. Reference scripts for different cloud providers are in `scripts/backup/`:
| Script | Provider | Tool |
|--------|----------|------|
| `s3-minio.sh` | S3-compatible (UpCloud, MinIO, Wasabi) | `minio/mc` |
| `aws-s3.sh` | AWS S3 | `aws` CLI |
| `azure-blob.sh` | Azure Blob Storage | `az` CLI |
| `gcp-gcs.sh` | GCP Cloud Storage | `gsutil` |
### Adding a New Cloud Provider
To add support for a new cloud (e.g., `oci-dev` for Oracle Cloud):
1. **Cluster config**: `clusters/oci-dev.yaml` — clusterName, domain, trustedIPs, cloudProvider
2. **Overlay value files** in `infra/values/oci-dev/`:
- `traefik-values.yaml` — LB annotations, proxy protocol config
- `keycloak-values.yaml` — hostname
- `grafana-values.yaml` — hostname
- `gitea-values.yaml``storageClass` for persistence + PostgreSQL
- `opencost-values.yaml` — pricing model or cloud billing integration
3. **Kustomize overlay**: `infra/overlays/oci-dev/kustomization.yaml` — patch `valueFiles[1]` for each Application
4. **App-of-apps**: `_app-of-apps-oci-dev.yaml` — points to `infra/overlays/oci-dev`
5. **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
@@ -2111,6 +1753,6 @@ team: platform
--- ---
**Last Updated**: 2026-04-22 **Last Updated**: 2026-04-16
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Version**: 1.0.0 **Version**: 1.0.0

View File

@@ -1,206 +0,0 @@
# Vault Secrets Operator (VSO) Reference
## Overview
The platform uses HashiCorp Vault Secrets Operator (VSO) to sync secrets from Vault KV v2 to native Kubernetes Secrets. This replaces the previous SealedSecrets workflow.
**Key benefit**: Secret values can be rotated via Vault UI/CLI without a git commit. Only new VaultStaticSecret CRDs need to be committed.
## Architecture
```
Vault (KV v2) VSO K8s Secret
kv/{namespace}/{name} --> VaultStaticSecret CRD --> Secret in namespace
(polls every 30s)
```
- **Vault**: Standalone instance in `vault` namespace, KV v2 at `kv/`
- **VSO**: Deployed in `vault-secrets-operator-system` namespace via ArgoCD
- **Auth**: Kubernetes auth method — each namespace has its own ServiceAccount + VaultAuth CRD
## KV Path Convention
```
kv/{namespace}/{secret-name}
```
Examples:
- `kv/homepage/homepage-widget-credentials`
- `kv/argocd/forte-helm-repo`
- `kv/gitea/gitea-smtp-secret`
- `kv/keycloak/keycloak-credentials`
## Vault Policy Structure
Each namespace gets a read-only policy:
```hcl
# Policy: ns-{namespace}
path "kv/data/{namespace}/*" {
capabilities = ["read"]
}
path "kv/metadata/{namespace}/*" {
capabilities = ["read", "list"]
}
```
## Kubernetes Auth Roles
Each namespace has a bound ServiceAccount:
```
Role: ns-{namespace}
bound_service_account_names: vault-auth-{namespace}
bound_service_account_namespaces: {namespace}
policies: ns-{namespace}
audience: vault
ttl: 1h
```
## CRD Reference
### VaultAuth
Per-namespace auth binding. One per namespace.
```yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: {namespace}
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-{namespace}
serviceAccount: vault-auth-{namespace}
audiences:
- vault
```
Each VaultAuth requires a corresponding ServiceAccount:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-{namespace}
namespace: {namespace}
```
### VaultStaticSecret
One per secret. Syncs a Vault KV path to a K8s Secret.
```yaml
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: {secret-name}
namespace: {namespace}
spec:
type: kv-v2
mount: kv
path: {namespace}/{secret-name}
destination:
name: {secret-name} # K8s Secret name (must match what apps expect)
create: true
type: Opaque # Optional, defaults to Opaque
labels: # Optional, for secrets that need labels
some-label: "value"
refreshAfter: 30s
vaultAuthRef: vault-auth
```
## Special Labels
Some secrets require specific labels for correct operation:
| Secret | Label | Purpose |
|--------|-------|---------|
| `renovate-env` | `allowedToBeCloned: "true"` | Kyverno secret-cloner policy |
| `gitea-smtp-secret` | `allowedToBeCloned: "true"` | Kyverno secret-cloner policy |
| `forte-helm-repo` | `argocd.argoproj.io/secret-type: repository` | ArgoCD repository recognition |
| `forte10x-repo-creds` | `argocd.argoproj.io/secret-type: repository` | ArgoCD repository recognition |
| `mcp10x-repo-creds` | `argocd.argoproj.io/secret-type: repository` | ArgoCD repository recognition |
These are set in `destination.labels` of the VaultStaticSecret CRD.
## Namespaces & Secrets Map
| Namespace | Secrets |
|-----------|---------|
| `homepage` | homepage-widget-credentials |
| `renovate` | renovate-env |
| `gitea` | gitea-credentials, gitea-backup-s3, gitea-smtp-secret, gitea-runner-token |
| `keycloak` | keycloak-credentials, microsoft-idp-credentials (overlay) |
| `argocd` | forte-helm-repo, forte10x-repo-creds, mcp10x-repo-creds, argocd-notifications-secret |
| `mcp10x` | app-credentials |
| `ts-mcp` | ts-mcp-secrets |
| `argocd-mcp` | auth-oidc, argocd-mcp-credentials |
| `dot-ai` | dot-ai-secrets |
| `music-man` | musicman-credentials |
## Common Operations
### Add a new secret
1. Write to Vault:
```bash
vault kv put kv/{namespace}/{secret-name} key1=val1 key2=val2
```
2. Create VaultStaticSecret YAML (see template above)
3. Add to kustomization.yaml in the appropriate directory
4. Commit and push — ArgoCD syncs the CRD, VSO creates the K8s Secret
### Rotate a secret value
No git commit needed:
```bash
vault kv put kv/{namespace}/{secret-name} key1=new-val1 key2=new-val2
```
VSO picks up changes within 30 seconds.
### Check sync status
```bash
# VaultAuth status
kubectl get vaultauth -n {namespace}
# VaultStaticSecret status
kubectl get vaultstaticsecret -n {namespace}
# Verify K8s Secret exists with correct keys
kubectl get secret {name} -n {namespace} -o jsonpath='{.data}' | jq
```
### Troubleshooting
1. **VaultAuth not authenticating**: Check ServiceAccount exists, Vault role matches SA name/namespace
2. **VaultStaticSecret not syncing**: Check `kubectl describe vaultstaticsecret {name} -n {ns}` for events
3. **Secret missing keys**: Verify Vault KV path has all expected keys: `vault kv get kv/{ns}/{name}`
4. **Permission denied**: Verify Vault policy allows read on `kv/data/{ns}/*`
## File Locations
| Type | Location |
|------|----------|
| VSO ArgoCD Application | `infra/base/vault-secrets-operator/` |
| VSO Helm values | `infra/values/base/vault-secrets-operator-values.yaml` |
| Vault policies script | `scripts/vault-setup-policies.sh` |
| Seed script | `scripts/seed-vault-from-cluster.sh` |
| VaultAuth + VaultStaticSecret | Alongside ArgoCD Application in each component directory |
## Setup Scripts
```bash
# Create all Vault policies and auth roles
./scripts/vault-setup-policies.sh
# Seed Vault KV from existing K8s Secrets
./scripts/seed-vault-from-cluster.sh
```

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -22,7 +22,6 @@ spec:
releaseName: gitea releaseName: gitea
valueFiles: valueFiles:
- $values/infra/values/base/gitea-values.yaml - $values/infra/values/base/gitea-values.yaml
- $values/infra/values/upc-dev/gitea-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD

View File

@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: gitea-backup-s3
namespace: gitea
spec:
type: kv-v2
mount: kv
path: gitea/gitea-backup-s3
destination:
name: gitea-backup-s3
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: gitea-credentials
namespace: gitea
spec:
type: kv-v2
mount: kv
path: gitea/gitea-credentials
destination:
name: gitea-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: gitea-runner-token
namespace: gitea
spec:
type: kv-v2
mount: kv
path: gitea/gitea-runner-token
destination:
name: gitea-runner-token
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,17 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: gitea-smtp-secret
namespace: gitea
spec:
type: kv-v2
mount: kv
path: gitea/gitea-smtp-secret
destination:
name: gitea-smtp-secret
create: true
type: Opaque
labels:
allowedToBeCloned: "true"
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,10 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gitea.yaml
- vault-auth.yaml
- gitea-credentials-vault.yaml
- gitea-backup-s3-vault.yaml
- gitea-smtp-secret-vault.yaml
- gitea-runner-token-vault.yaml
# Removed: gitea-*-sealed.yaml (migrated to VSO)

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-gitea
namespace: gitea
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: gitea
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-gitea
serviceAccount: vault-auth-gitea
audiences:
- vault

View File

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

View File

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

View File

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

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

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: homepage-widget-credentials
namespace: homepage
spec:
type: kv-v2
mount: kv
path: homepage/homepage-widget-credentials
destination:
name: homepage-widget-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

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

@@ -1,8 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- homepage.yaml
- vault-auth.yaml
- homepage-widget-credentials-vault.yaml
- homepage-extra-rbac.yaml
# Removed: homepage-widget-credentials-sealed.yaml (migrated to VSO)

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-homepage
namespace: homepage
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: homepage
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-homepage
serviceAccount: vault-auth-homepage
audiences:
- vault

View File

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

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

View File

@@ -15,7 +15,7 @@ spec:
project: default project: default
sources: sources:
- repoURL: registry-1.docker.io/bitnamicharts - repoURL: https://charts.bitnami.com/bitnami
chart: keycloak chart: keycloak
targetRevision: "25.2.0" targetRevision: "25.2.0"
helm: helm:
@@ -47,7 +47,3 @@ spec:
kind: CronJob kind: CronJob
jsonPointers: jsonPointers:
- /spec/jobTemplate/spec/template/spec/containers/0/args - /spec/jobTemplate/spec/template/spec/containers/0/args
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates

View File

@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: keycloak-credentials
namespace: keycloak
spec:
type: kv-v2
mount: kv
path: keycloak/keycloak-credentials
destination:
name: keycloak-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth

View File

@@ -1,7 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- keycloak.yaml
- vault-auth.yaml
- keycloak-credentials-vault.yaml
# Removed: keycloak-credentials-sealed.yaml (migrated to VSO)

View File

@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-keycloak
namespace: keycloak
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: keycloak
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-keycloak
serviceAccount: vault-auth-keycloak
audiences:
- vault

View File

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

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