119 Commits

Author SHA1 Message Date
15b2fe1010 clusters 2026-04-18 19:29:59 +02:00
ae1c60cee3 multi-cluster 2026-04-18 19:26:51 +02:00
0d64249858 Merge branch 'main' of https://git.forteapps.net/Forte/launchpad into feature/multicluster 2026-04-18 16:09:13 +02:00
72a65f0e06 client cloner (#3)
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
Reviewed-on: #3
Reviewed-by: gitea_admin <admin@forteapps.net>
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-17 13:42:44 +00:00
44fc242ae8 doc
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
2026-04-17 11:43:50 +02:00
b2f601e950 doc
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-17 11:42:46 +02:00
f8b17cc030 log level info renovate 2026-04-17 10:59:52 +02:00
6639d0e3ff renovate prs
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 5m15s
2026-04-17 09:58:52 +02:00
4485731ab5 smtp+starttls 2026-04-16 15:57:59 +02:00
439b8516f0 smtps auth 2026-04-16 15:46:54 +02:00
0eccd2d439 smtp auth 2026-04-16 15:43:10 +02:00
3e1029a557 mail notification 2026-04-16 15:39:51 +02:00
61c2801e0a smtp 2026-04-16 15:32:10 +02:00
8902a0e51e Merge pull request 'SMTP config Gitea' (#2) from feature/smtp into main
Reviewed-on: #2
2026-04-16 13:17:28 +00:00
4486279eab smtp config 2026-04-16 15:13:18 +02:00
020dfeffd4 client secret fixes
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 6m6s
2026-04-16 15:04:27 +02:00
7e10954a8f client secret bootstrapping
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 39m32s
2026-04-16 13:55:13 +02:00
88c29565b6 smtp 2026-04-16 10:42:35 +02:00
87ee0588a7 renovate pr targets 2026-04-15 16:33:58 +02:00
db8a1de797 10x repo PRs 2026-04-15 13:46:13 +02:00
177150e069 gitea protocol mapper
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
2026-04-15 13:27:14 +02:00
c63a9242f0 renovate loglevel 2026-04-14 12:44:47 +02:00
1d43ecddad renovate daily and more mem 2026-04-14 12:26:46 +02:00
a702a16155 renovate token 2026-04-14 12:17:53 +02:00
8b403736a9 secret renovate 2026-04-14 12:14:43 +02:00
0e8524b84a renovate
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-14 12:05:14 +02:00
58ccc9fd2e Merge branch 'main' of https://git.forteapps.net/Forte/launchpad 2026-04-14 10:53:49 +02:00
08d870d44c oauth fix 2026-04-14 10:53:45 +02:00
2b7d441803 Update infra/gitea.yaml
ignoreDifferences
2026-04-14 08:33:47 +00:00
e74a8cb9d8 Update infra/values/gitea-values.yaml
ENABLE_BASIC_AUTH_CHALLENGE: true
2026-04-14 08:23:09 +00:00
f5166b3797 readme 2026-04-13 16:08:30 +02:00
18fb0ca3da repo names fix
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-13 16:08:01 +02:00
4abd528b19 launchpad 2026-04-13 15:55:25 +02:00
a9a3e0e8ab Merge branch 'main' of https://git.forteapps.net/Forte/launchpad
Some checks failed
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-13 15:54:23 +02:00
827213c883 migration 2026-04-13 15:54:14 +02:00
02d5b3eb5a Update .github/workflows/docs.yml 2026-04-09 09:11:16 +00:00
f90833711d Update .github/workflows/docs.yml 2026-04-09 09:05:12 +00:00
1c6a0a1b2f Update .github/workflows/docs.yml 2026-04-09 09:02:58 +00:00
665e4020ba Update .github/workflows/docs.yml 2026-04-09 09:01:37 +00:00
643c0aaf9b Update .github/workflows/docs.yml 2026-04-09 09:00:24 +00:00
61184f6fdf Update .github/workflows/docs.yml 2026-04-09 08:58:00 +00:00
84698ab743 Update .github/workflows/docs.yml 2026-04-09 08:54:15 +00:00
snothub
cb548ee09a gitea actions 2026-04-08 12:56:07 +02:00
snothub
9f130a8dc4 gitea runner token 2026-04-08 12:51:11 +02:00
snothub
ab136ea8f2 gitea recreate 2026-04-08 12:44:56 +02:00
snothub
b3d4a26a07 gitea runners 2026-04-08 12:40:13 +02:00
snothub
5e205944c6 kc creds 2026-04-08 12:21:37 +02:00
snothub
463a96054d kyverno policy remove 2026-04-08 12:12:09 +02:00
snothub
118cae656a gitea pg 2026-04-08 12:04:13 +02:00
snothub
2e725ffcdd gitea 2026-04-08 12:00:15 +02:00
snothub
dcfa104948 disable results cache 2026-04-07 10:26:27 +02:00
snothub
43699b9bbd MkDocs 2026-04-04 17:46:08 +02:00
snothub
97aeba8275 docs 2026-04-04 17:30:16 +02:00
snothub
f7897bc2bf kc resources 2026-04-02 22:56:18 +02:00
snothub
b281556808 mcp def scope 2026-04-02 22:45:15 +02:00
snothub
010d29ff11 argo notifications 2026-03-29 21:44:25 +02:00
snothub
369d5453e0 notification fix 2026-03-29 21:07:26 +02:00
snothub
212dc66fab PSS dash 2026-03-29 16:20:48 +02:00
snothub
38433f62ce del mcpcoder 2026-03-29 15:20:27 +02:00
snothub
ede14d9ec6 degraded message fix 2026-03-29 14:57:30 +02:00
snothub
9edbe3d0ef argocd status sync update 2026-03-29 14:53:55 +02:00
snothub
e199b00137 dash opt 2026-03-27 14:25:34 +01:00
snothub
ce5094c1c8 egress 2026-03-27 11:49:03 +01:00
snothub
5e8448cfd2 dashboards json 2026-03-27 09:05:06 +01:00
snothub
875db6721e keycloak resource lowering 2026-03-26 18:34:10 +01:00
snothub
87cd2401c5 resource lowering on monitoring 2026-03-26 18:31:28 +01:00
snothub
e938bf2467 new grafana dash 2026-03-26 16:04:32 +01:00
snothub
ca8127802b doc 2026-03-26 15:25:14 +01:00
snothub
1609f6afde exclude trivy-system from kyverno policies 2026-03-26 13:34:00 +01:00
snothub
c33abcc357 default port 2026-03-26 13:26:31 +01:00
snothub
8029b7816d rename annotation 2026-03-26 13:25:53 +01:00
snothub
5640a5ca4a sidecar port 2026-03-26 13:24:06 +01:00
snothub
b9d8470a52 oauth env sidecar 2026-03-26 11:48:28 +01:00
279bc8b273 traefik default time span 2026-03-23 13:11:28 +01:00
c914498590 line format 2026-03-23 12:27:58 +01:00
5d8437bd01 filter logs 2026-03-23 12:24:27 +01:00
684b35c009 service graph 2026-03-23 12:16:44 +01:00
db8fb09fe1 sm 2026-03-23 12:10:31 +01:00
7a204e367c time ranges 2026-03-23 11:55:00 +01:00
bdfada0838 opencost disable ui 2026-03-23 11:49:30 +01:00
7269eb3121 title 2026-03-23 11:42:25 +01:00
fca94cde94 fix 2026-03-23 11:41:13 +01:00
161dc52d4a refresh 2026-03-23 11:07:52 +01:00
76b39241c1 oc yaml indenting 2026-03-23 10:50:44 +01:00
f50f03d8e0 typo 2026-03-23 10:44:46 +01:00
4266327d35 rates fix 2026-03-23 10:41:21 +01:00
8f71d159ff currency and rates 2026-03-23 10:36:02 +01:00
f8ecc54b86 panel 2026-03-23 09:39:34 +01:00
b1b75c77c5 cost values 2026-03-23 09:37:44 +01:00
c2aa680a0f opencost grafana json 2026-03-23 09:23:16 +01:00
d0ab490eb5 datasource 2026-03-22 16:01:04 +01:00
fd0e578131 opencost scraping 2026-03-22 15:51:11 +01:00
c6bc723b8a opencost scrapes 2026-03-22 00:17:12 +01:00
1983c80f15 opencost 2026-03-21 23:55:33 +01:00
5dc12cfaa2 new api token 2026-03-21 23:36:33 +01:00
258ece5f85 dot ai secrets 2026-03-21 23:32:32 +01:00
2f88b2d16c svc graph fix 2026-03-20 14:41:10 +01:00
b4ffae5078 service graph 2026-03-20 14:31:52 +01:00
7aa69f6a7f cleanup 2026-03-20 14:29:32 +01:00
d394dfd55e host fix 2026-03-20 14:23:46 +01:00
f728f9dbd3 Tempo doc 2026-03-20 14:22:14 +01:00
7522b88cfb fix tempo 2026-03-20 14:19:55 +01:00
afb39f99a7 Grafana Tempo 2026-03-20 14:04:20 +01:00
e4f8f2c071 traefik grafana dash 2026-03-20 13:46:13 +01:00
2ecd0c8a44 traefik metrics 2026-03-20 13:32:06 +01:00
3c81fd1e3a cleanup 2026-03-20 13:11:38 +01:00
b665faaa7b sidecar image ref 2026-03-20 13:06:11 +01:00
5071110c72 repo url fix 2026-03-20 13:02:48 +01:00
016e70a998 argocd repo secret 2026-03-20 12:59:13 +01:00
Danijel Simeunovic
8b1931fa9d traefik access logging 2026-03-20 11:12:48 +01:00
Danijel Simeunovic
ec4082de93 otel 2026-03-20 11:09:42 +01:00
Danijel Simeunovic
d50b790082 otel 2026-03-20 11:08:04 +01:00
Danijel Simeunovic
29e644510c traefik tracing 2026-03-20 10:57:18 +01:00
Danijel Simeunovic
3264f879b0 fortedigital/forte-helm 2026-03-20 09:42:32 +01:00
Danijel Simeunovic
36460b5cac kcprom 2026-03-19 20:28:42 +01:00
Danijel Simeunovic
4c0ec63ec3 apikey 2026-03-19 12:51:07 +01:00
Danijel Simeunovic
2b71f63740 dot-ai 2026-03-19 12:46:51 +01:00
Danijel Simeunovic
0de4e381c7 mm secret 2026-03-19 10:38:02 +01:00
Danijel Simeunovic
2c0b6b5ea9 authn: public paths 2026-03-18 22:41:12 +01:00
97 changed files with 7873 additions and 1498 deletions

34
.github/workflows/docs.yml vendored Normal file
View File

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

4
.gitignore vendored
View File

@@ -6,7 +6,6 @@
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
.github/
private/ private/
.helm/ .helm/
temp/ temp/
@@ -15,4 +14,5 @@ CLAUDE.md
.claude/ .claude/
devbox.d/ devbox.d/
devbox.lock devbox.lock
.devbox/ .devbox/
bash.exe.stackdump

7
.project-standards.yaml Normal file
View File

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

View File

@@ -27,8 +27,8 @@
### For New Developers ### For New Developers
```bash ```bash
# 1. Clone repositories # 1. Clone repositories
git clone https://github.com/fortedigital/sturdy-adventure.git git clone https://git.forteapps.net/Forte/launchpad.git
git clone git@github.com:fortedigital/helm-values.git git clone ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
# 2. Read the guides # 2. Read the guides
# - Start: docs/GITOPS-ARCHITECTURE.md # - Start: docs/GITOPS-ARCHITECTURE.md
@@ -57,10 +57,10 @@ 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, Sealed Secrets - **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, and alerting - **Monitoring**: Full observability stack with metrics, logs, traces, and alerting
- **Secrets**: Sealed Secrets for secure Git storage - **Secrets**: Sealed Secrets for secure Git storage
### Key Features ### Key Features
@@ -72,7 +72,7 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
**Policy Enforcement**: Kyverno ensures security and compliance **Policy Enforcement**: Kyverno ensures security and compliance
**Authentication**: Automatic sidecar injection (token & OIDC support) **Authentication**: Automatic sidecar injection (token & OIDC support)
**TLS Everywhere**: Automatic Let's Encrypt certificates **TLS Everywhere**: Automatic Let's Encrypt certificates
**Full Observability**: Prometheus, Grafana, Loki integration **Full Observability**: Prometheus, Grafana, Loki, Tempo integration
--- ---
@@ -83,18 +83,26 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern) ├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern)
├── infra/ # Infrastructure ArgoCD Applications ├── infra/ # Infrastructure ArgoCD Applications (Kustomize multi-cluster)
│ ├── enterprise-apps.yaml # Manages all apps in apps/ folder │ ├── base/ # Base ArgoCD Application manifests (EU defaults)
│ ├── traefik-application.yaml │ ├── kustomization.yaml
│ ├── cert-manager-application.yaml │ ├── traefik-application.yaml
│ ├── kyverno.yaml │ ├── keycloak.yaml
│ ├── prometheus.yaml │ ├── grafana.yaml
│ ├── grafana.yaml │ ├── gitea.yaml
│ ├── loki.yaml │ ├── gitea-actions.yaml
│ ├── fluent-bit.yaml │ ├── tempo.yaml
│ ├── trivy.yaml │ ├── renovate.yaml
│ ├── sealedsecrets.yaml │ ├── ... # All other Application manifests
│ │ └── secrets.yaml
│ ├── overlays/ # Per-cluster overrides
│ │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is)
│ │ └── upc-prod/ # UpCloud Prod cluster (patches value paths)
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides │ └── values/ # Helm value overrides
│ ├── base/ # Shared values (all clusters)
│ ├── upc-dev/ # UpCloud Dev-specific values
│ └── upc-prod/ # UpCloud Prod-specific values
├── apps/ # Business Applications ├── apps/ # Business Applications
│ ├── mcp10x.yaml │ ├── mcp10x.yaml
@@ -136,9 +144,9 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
| Repository | Purpose | Who Edits | How Often | | Repository | Purpose | Who Edits | How Often |
|------------|---------|-----------|-----------| |------------|---------|-----------|-----------|
| **[sturdy-adventure](https://github.com/fortedigital/sturdy-adventure.git)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often | | **[launchpad](https://git.forteapps.net/Forte/launchpad)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often |
| **[forte-helm](https://github.com/snothub/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely | | **[forte-helm](https://git.forteapps.net/Forte/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely |
| **[helm-values](git@github.com:fortedigital/helm-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes | | **[helm-values](ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes |
### GitOps Workflow ### GitOps Workflow
@@ -331,7 +339,10 @@ kubectl patch application myapp -n argocd \
| **Prometheus** | Metrics | `monitoring` | 1 | | **Prometheus** | Metrics | `monitoring` | 1 |
| **Grafana** | Dashboards | `monitoring` | 1 | | **Grafana** | Dashboards | `monitoring` | 1 |
| **Loki** | Logs | `monitoring` | 1 | | **Loki** | Logs | `monitoring` | 1 |
| **Tempo** | Distributed tracing | `monitoring` | 1 |
| **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet | | **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet |
| **OpenCost** | Cost monitoring | `monitoring` | 1 |
| **Renovate** | Dependency updates | `renovate` | CronJob |
| **Trivy** | Vulnerability scanning | `trivy-system` | 1 | | **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)
@@ -350,7 +361,7 @@ kubectl patch application myapp -n argocd \
## 📖 Key Concepts ## 📖 Key Concepts
### App-of-Apps Pattern ### App-of-Apps Pattern
`_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Each YAML in `infra/` becomes a child Application managed by ArgoCD. `_app-of-apps.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:
@@ -449,14 +460,14 @@ Documentation lives in `docs/`. To update:
### Current Environment ### Current Environment
- **Provider**: UpCloud Managed Kubernetes - **Provider**: UpCloud Managed Kubernetes
- **Environment**: Production (internal use only) - **Environment**: Production (internal use only)
- **Cluster**: Single cluster - **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
- **Auth**: Disabled for ArgoCD (internal access) - **Auth**: Disabled for ArgoCD (internal access)
- **Backup**: None (cluster rebuildable via GitOps) - **Backup**: None (cluster rebuildable via GitOps)
### Known Limitations ### Known Limitations
- No automated backups (yet) - No automated backups (yet)
- Secret rotation not automated - Secret rotation not automated
- Single cluster (no multi-cluster setup) - 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)
@@ -470,10 +481,11 @@ Documentation lives in `docs/`. To update:
- [Kyverno Documentation](https://kyverno.io/docs/) - [Kyverno Documentation](https://kyverno.io/docs/)
- [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/)
- [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) - [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
### Related Repositories ### Related Repositories
- [forte-helm](https://github.com/snothub/forte-helm) - Helm chart templates - [forte-helm](https://github.com/fortedigital/forte-helm) - Helm chart templates
- [helm-values](git@github.com:fortedigital/helm-values.git) - Application values - [helm-values](git@github.com:fortedigital/helm-values.git) - Application values
--- ---

View File

@@ -18,9 +18,9 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: infra/overlays/us path: infra/overlays/upc-dev
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: default namespace: default

View File

@@ -20,7 +20,7 @@ spec:
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD
path: infra/overlays/eu path: infra/overlays/upc-prod
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: default namespace: default

View File

@@ -16,14 +16,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: https://github.com/snothub/forte-helm - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/argocd-mcp/values.yaml - $values/argocd-mcp/values.yaml
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -35,7 +35,7 @@ spec:
releaseName: dot-ai-stack releaseName: dot-ai-stack
valueFiles: valueFiles:
- $values/infra/values/base/dot-ai-stack-values.yaml - $values/infra/values/base/dot-ai-stack-values.yaml
- $values/infra/values/eu/dot-ai-stack-values.yaml - $values/infra/values/upc-dev/dot-ai-stack-values.yaml
- repoURL: git@github.com:fortedigital/sturdy-adventure.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD

View File

@@ -17,14 +17,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: https://github.com/snothub/forte-helm - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/mcp10x/values.yaml - $values/mcp10x/values.yaml
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

@@ -1,39 +1,42 @@
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
name: mcpcoder name: opencost
namespace: argocd namespace: argocd
annotations: annotations:
argocd.argoproj.io/sync-wave: "1" argocd.argoproj.io/sync-wave: "1"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels: labels:
app.kubernetes.io/name: mcpcoder app.kubernetes.io/name: opencost
app.kubernetes.io/part-of: apps app.kubernetes.io/part-of: monitoring
app.kubernetes.io/managed-by: argocd app.kubernetes.io/managed-by: argocd
finalizers: finalizers:
- resources-finalizer.argocd.argoproj.io - resources-finalizer.argocd.argoproj.io
spec: spec:
project: default project: default
sources:
- repoURL: https://github.com/snothub/forte-helm
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/mcpcoder/values.yaml
- repoURL: git@github.com:fortedigital/helm-values.git sources:
- repoURL: https://opencost.github.io/opencost-helm-chart
chart: opencost
targetRevision: "1.42.0"
helm:
releaseName: opencost
valueFiles:
- $values/infra/values/opencost-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: mcpcoder namespace: monitoring
syncPolicy: syncPolicy:
automated: automated:
prune: true prune: true
selfHeal: true selfHeal: true
allowEmpty: false
syncOptions: syncOptions:
- CreateNamespace=true - CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

@@ -17,14 +17,14 @@ metadata:
spec: spec:
project: default project: default
sources: sources:
- repoURL: https://github.com/snothub/forte-helm - repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
valueFiles: valueFiles:
- $values/musicman/values.yaml - $values/musicman/values.yaml
- repoURL: git@github.com:fortedigital/helm-values.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

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

View File

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

View File

@@ -4,11 +4,11 @@ resources:
- ../../base - ../../base
patches: patches:
# dot-ai-stack: swap eu → us # dot-ai-stack: swap upc-dev → upc-prod
- target: - target:
kind: Application kind: Application
name: dot-ai-stack name: dot-ai-stack
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/us/dot-ai-stack-values.yaml value: $values/infra/values/upc-prod/dot-ai-stack-values.yaml

View File

@@ -17,7 +17,7 @@ echo "Bootstrapping cluster: ${clusterName} (${CLUSTER})..."
Bootstrap() Bootstrap()
{ {
ArgoCd ArgoCd
Github # Github
} }

View File

@@ -0,0 +1,88 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: gitea-backup
namespace: gitea
spec:
schedule: "0 3 * * *" # daily at 03:00 UTC
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 1
activeDeadlineSeconds: 1800
template:
spec:
restartPolicy: Never
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
# Must run on the same node as Gitea to share the RWO volume
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: gitea
topologyKey: kubernetes.io/hostname
initContainers:
- name: gitea-dump
image: gitea/gitea:1.25.4
command:
- sh
- -c
- |
gitea dump \
-c /data/gitea/conf/app.ini \
-f /backup/gitea-dump.zip \
-t /tmp/gitea-dump && \
echo "Dump completed: $(ls -lh /backup/gitea-dump.zip)"
volumeMounts:
- name: data
mountPath: /data
readOnly: true
- name: backup
mountPath: /backup
- name: tmp
mountPath: /tmp/gitea-dump
containers:
- name: upload
image: minio/mc:latest
env:
- name: HOME
value: /tmp
command:
- sh
- -c
- |
mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
KEY="gitea-dump-${TIMESTAMP}.zip"
echo "Uploading ${KEY}..."
mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \
echo "Upload complete."
# Prune backups older than 7 days
echo "Pruning backups older than 7 days..."
mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true
echo "Pruning complete."
envFrom:
- secretRef:
name: gitea-backup-s3
volumeMounts:
- name: backup
mountPath: /backup
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: gitea-shared-storage
- name: backup
emptyDir:
sizeLimit: 5Gi
- name: tmp
emptyDir:
sizeLimit: 5Gi

View File

@@ -0,0 +1,13 @@
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
name: gitea-ssh
namespace: gitea
spec:
entryPoints:
- giteassh
routes:
- match: HostSNI(`*`)
services:
- name: gitea-ssh
port: 22

View File

@@ -0,0 +1,37 @@
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: deny-external-egress
namespace: trivy-system
labels:
app.kubernetes.io/managed-by: argocd
app.kubernetes.io/part-of: network-policies
spec:
endpointSelector: {}
egress:
# Allow DNS resolution
- toEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
# Allow cluster-internal traffic (RFC1918)
- toCIDR:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
# Allow Trivy vulnerability DB downloads (ghcr.io OCI registry)
- toFQDNs:
- matchName: ghcr.io
- matchName: pkg-containers.githubusercontent.com
toPorts:
- ports:
- port: "443"
protocol: TCP

View File

@@ -10,7 +10,7 @@ metadata:
policies.kyverno.io/severity: medium policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >- policies.kyverno.io/description: >-
Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. A NetworkPolicy is generated to restrict ingress to the sidecar port only. Injects an auth sidecar container into Pods annotated with policies.forteapps.io/auth: "true". Supports three auth modes controlled by the policies.forteapps.io/auth-type annotation: "token" (default), "oidc", and "mcp". In token mode the sidecar reads credentials from a mounted Secret volume. In OIDC mode the sidecar uses OpenID Connect with authority and client-id provided via required annotations (policies.forteapps.io/auth-oidc-authority and policies.forteapps.io/auth-oidc-client-id) and secrets from an auth-oidc Secret. In MCP mode the sidecar implements OAuth 2.0 for MCP servers per RFC 9728 (Protected Resource Metadata) and RFC 7591 (Dynamic Client Registration), configured via policies.forteapps.io/auth-mcp-resource and policies.forteapps.io/auth-mcp-authority annotations. The sidecar port defaults to 9001 and can be overridden via the policies.forteapps.io/auth-port annotation. A NetworkPolicy is generated to restrict ingress to the sidecar port only.
spec: spec:
background: false background: false
rules: rules:
@@ -119,21 +119,26 @@ spec:
- name: appPort - name: appPort
variable: variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate: mutate:
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
- name: authn - name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
ports: ports:
- containerPort: 8080 - containerPort: "{{ sidecarPort }}"
name: auth name: auth
protocol: TCP protocol: TCP
env: env:
- name: AUTH_LISTEN_ADDR - name: AUTH_LISTEN_ADDR
value: ":8080" value: ":{{ sidecarPort }}"
- name: AUTH_UPSTREAM_URL - name: AUTH_UPSTREAM_URL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}"
- name: AUTH_PUBLIC_PATHS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
- name: AUTH_TOKEN_FILE - name: AUTH_TOKEN_FILE
value: "/etc/auth/tokens" value: "/etc/auth/tokens"
- name: AUTH_MODE - name: AUTH_MODE
@@ -152,13 +157,13 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 8080 port: "{{ sidecarPort }}"
initialDelaySeconds: 2 initialDelaySeconds: 2
periodSeconds: 5 periodSeconds: 5
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 8080 port: "{{ sidecarPort }}"
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
securityContext: securityContext:
@@ -195,22 +200,25 @@ spec:
- name: appPort - name: appPort
variable: variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate: mutate:
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
- name: authn - name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8080 - containerPort: "{{ sidecarPort }}"
name: auth name: auth
protocol: TCP protocol: TCP
env: env:
- name: AUTH_MODE - name: AUTH_MODE
value: "oidc" value: "oidc"
- name: AUTH_LISTEN_ADDR - name: AUTH_LISTEN_ADDR
value: ":8080" value: ":{{ sidecarPort }}"
- name: AUTH_LOG_LEVEL - name: AUTH_LOG_LEVEL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}"
- name: AUTH_UPSTREAM_URL - name: AUTH_UPSTREAM_URL
@@ -225,6 +233,8 @@ spec:
value: "{{ regex_replace_all('https?://[^/]*', request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-callback-path\", '') }}" value: "{{ regex_replace_all('https?://[^/]*', request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-callback-path\", '') }}"
- name: AUTH_OIDC_SCOPES - name: AUTH_OIDC_SCOPES
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-scopes\" || 'openid,profile,email' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-scopes\" || 'openid,profile,email' }}"
- name: AUTH_PUBLIC_PATHS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-public-paths\" || '/healthz' }}"
- name: AUTH_OIDC_COOKIE_SECRET - name: AUTH_OIDC_COOKIE_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@@ -233,8 +243,8 @@ spec:
- name: AUTH_OIDC_CLIENT_SECRET - name: AUTH_OIDC_CLIENT_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: auth-oidc name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret\" || 'auth-oidc' }}"
key: client-secret key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oidc-credentials-secret-key\" || 'client-secret' }}"
resources: resources:
limits: limits:
cpu: 50m cpu: 50m
@@ -245,13 +255,13 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 8080 port: "{{ sidecarPort }}"
initialDelaySeconds: 2 initialDelaySeconds: 2
periodSeconds: 5 periodSeconds: 5
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 8080 port: "{{ sidecarPort }}"
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
securityContext: securityContext:
@@ -283,22 +293,25 @@ spec:
- name: appPort - name: appPort
variable: variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000` jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate: mutate:
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
- name: authn - name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'ghcr.io/snothub/stunning-memory' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}" image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 8080 - containerPort: "{{ sidecarPort }}"
name: auth name: auth
protocol: TCP protocol: TCP
env: env:
- name: AUTH_MODE - name: AUTH_MODE
value: "mcp" value: "mcp"
- name: AUTH_LISTEN_ADDR - name: AUTH_LISTEN_ADDR
value: ":8080" value: ":{{ sidecarPort }}"
- name: AUTH_LOG_LEVEL - name: AUTH_LOG_LEVEL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}"
- name: AUTH_UPSTREAM_URL - name: AUTH_UPSTREAM_URL
@@ -307,8 +320,10 @@ spec:
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-resource\" }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-resource\" }}"
- name: AUTH_MCP_AUTHORIZATION_SERVERS - name: AUTH_MCP_AUTHORIZATION_SERVERS
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-authority\" }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-authority\" }}"
- name: AUTH_PUBLIC_PATHS
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\" || 'read,write' }}" value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-mcp-scopes\" || 'profile' }}"
resources: resources:
limits: limits:
cpu: 50m cpu: 50m
@@ -319,13 +334,106 @@ spec:
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 8080 port: "{{ sidecarPort }}"
initialDelaySeconds: 2 initialDelaySeconds: 2
periodSeconds: 5 periodSeconds: 5
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /healthz path: /healthz
port: 8080 port: "{{ sidecarPort }}"
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
- name: inject-sidecar-oauth
skipBackgroundRequests: true
match:
any:
- resources:
kinds:
- Pod
annotations:
policies.forteapps.io/auth: "true"
policies.forteapps.io/auth-type: "oauth"
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
- argocd
- cert-manager
- monitoring
context:
- name: appPort
variable:
jmesPath: request.object.spec.containers[?name != 'authn'] | [0].ports[0].containerPort || `3000`
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
mutate:
patchStrategicMerge:
spec:
containers:
- name: authn
image: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image\" || 'git.forteapps.net/forte/auth-sidecar' }}:{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-image-version\" || 'latest' }}"
imagePullPolicy: Always
ports:
- containerPort: "{{ sidecarPort }}"
name: auth
protocol: TCP
env:
- name: AUTH_MODE
value: "oauth"
- name: AUTH_LISTEN_ADDR
value: ":{{ sidecarPort }}"
- name: AUTH_LOG_LEVEL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-log-level\" || 'info' }}"
- name: AUTH_UPSTREAM_URL
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-upstream-url\" || join('', ['http://localhost:', to_string(appPort)]) }}"
- name: AUTH_OAUTH_AUTHORITY
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-authority\" }}"
- name: AUTH_OAUTH_CLIENT_ID
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-client-id\" }}"
- name: AUTH_OAUTH_SCOPES
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-scopes\" || 'openid,profile,email' }}"
- name: AUTH_OAUTH_DELEGATION_ENABLED
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-enabled\" || 'false' }}"
- name: AUTH_OAUTH_DELEGATION_CLIENT_ID
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-client-id\" || '' }}"
- name: AUTH_OAUTH_DELEGATION_SCOPES
value: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-delegation-scopes\" || '' }}"
- name: AUTH_OAUTH_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret\" || 'auth-oauth' }}"
key: "{{ request.object.metadata.annotations.\"policies.forteapps.io/auth-oauth-credentials-secret-key\" || 'client-secret' }}"
- name: AUTH_OAUTH_DELEGATION_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: auth-oauth
key: delegation-client-secret
resources:
limits:
cpu: 50m
memory: 64Mi
requests:
cpu: 10m
memory: 32Mi
readinessProbe:
httpGet:
path: /healthz
port: "{{ sidecarPort }}"
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: "{{ sidecarPort }}"
initialDelaySeconds: 5 initialDelaySeconds: 5
periodSeconds: 10 periodSeconds: 10
securityContext: securityContext:
@@ -358,6 +466,10 @@ spec:
operator: In operator: In
value: value:
- CREATE - CREATE
context:
- name: sidecarPort
variable:
jmesPath: to_number(request.object.metadata.annotations."policies.forteapps.io/auth-port" || '9001')
generate: generate:
synchronize: false synchronize: false
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
@@ -376,5 +488,5 @@ spec:
- Ingress - Ingress
ingress: ingress:
- ports: - ports:
- port: 8080 - port: "{{ sidecarPort }}"
protocol: TCP protocol: TCP

View File

@@ -1,71 +0,0 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-deployment-owner
spec:
validationFailureAction: Audit
background: false
rules:
- name: check-pod-owner-is-replicaset-from-deployment
skipBackgroundRequests: true
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
- cert-manager
- monitoring
- argocd
- traefik-system
context:
- name: ownerReplicaSet
apiCall:
method: GET
urlPath: "/apis/apps/v1/namespaces/{{request.namespace}}/replicasets/{{request.object.metadata.ownerReferences[0].name}}"
jmesPath: "@"
preconditions:
all:
- key: "{{ request.object.metadata.ownerReferences || `[]` | [?kind=='ReplicaSet'] | length(@) }}"
operator: GreaterThanOrEquals
value: 1
validate:
allowExistingViolations: true
message: "Pods must be created through a Deployment resource."
deny:
conditions:
any:
- key: "{{ownerReplicaSet.metadata.ownerReferences[0].kind}}"
operator: NotEquals
value: Deployment
- name: deny-pods-without-replicaset-owner
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
- cert-manager
- monitoring
- argocd
- traefik-system
skipBackgroundRequests: true
validate:
allowExistingViolations: true
message: "Direct pod creation is not allowed. Pods must come from a Deployment managed by ArgoCD."
deny:
conditions:
all:
- key: "{{ request.object.metadata.ownerReferences || `[]` | [?kind=='ReplicaSet'] | length(@) }}"
operator: LessThan
value: 1

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: snothub-repo-creds
namespace: argocd
spec:
encryptedData:
sshPrivateKey: AgCBd+i8jXBDwvWM0YC9OWvNTKyynVpW6hF0F3aH0GXBIYxzFo1g9kMajE/Ce3bTl8DiVs7VGzPdI6lmJtSC3+fVMm4wavDGrUUbhUHSR/HnEqq51NVjxU1Uj+VRz550z9r6gB0VLAcYqN0WLKd/4Qn7tvQOmKsaXMd5jsIYpSB8nZK87awZ5niFmT8DIKu2DOzoDBeN7Yqrf1aELq3m3kaDxGcDMYSglB0xRU3fF8FYr5inCic1eTrCUdfIwnrBSublNxI9FrGnlylRC40XCPxNpy6zyoQt7yeJTNgRSvtfceqXAwh25mcvga+BfDBTLPQ0EDbCoNDnE9EyMUa4kWcXBTFZ1Qx5NGRz8HAjWoYDbuNOonl9ik5dvv9UKKA+/04ZEuPjsbdkBq9xKuowx0zLL1IVv/jeuViFdvNY6LKNu/hLwMn8aMlTLlIkB+1fsIqV7Vkva9Vk7IXNz575lMwIGUTo1dsK9FQ5+uIe2bsRnY8RJ3lpyndZQ1HDPh7P7KZLZPH8fUuAHH1UF74njMQyve79zCRcisBAewpXdq4UsYAYUQOluS1Ak+sFcIdQ0jHjfklGCcJnTvMyO7obIsPQSv39/bsCqQX6uisrTzcb0s7wnbzhcxf1gm7IyZMIhi2Vub3GoLCIMnb6ViO8k+itLUa5eZpoEeg+BpP1mgL7O2nVfrZYgueULgMSvN89ct/THITsFAR8614An3DCHSnZLv5ZmY7yC6rmO405IlrnjvfFqEt9MdqTgR7uTYDSdI7UjIFJp3rchzikF1pSDMu/siKmD/Vi9+S0KqBcSENz7EUppVuYIk0aRBqHZ9Awoe0qPIjp1AMg947FjIYXzkGk3Kz0P85fGwkktyfHNZdIrXvpDAIP2739Mr7Hde+EHpuajrhuBgozfless5PqLFfGWFnt5COW2HrdSrvrY56MXuJUfiV0nq3eEhCju9f/cA41VbxfO0Hj+KMCjxiL+MCgIt1eWD24P7GQHb0D7+JiuwqgcH0ZqXRaKFYSOJ3/U1o9RRX9v5yebNTm8ErQTSnIN5bNRgE+t8CrTulGVcpaL/rW+XW8cO8MXLC/R4eeNwVjoiK4yXSHpSjf5dF9MZ6bS6SSZPMOa2besEzzQIj0c/EkUsp/GmIF1utDbJVI2VZLFGklwRo92OJs3URXNCqbzzgte43Y9dJp2VdWyi3Zx32pXVWaNdOzeifVOASP7ags7Kbdfvoaa5CmTu17iRUHCOv6164/KFeDCfY=
type: AgCIKe+SzLHIp+6gjOVbD7wcpZkeg5UwgXabFjtonavkPbzX+txHWN7IH2HbDmd7TdpgGFiqMGFSQC8mOVXnj+Pw1XnI8trH6wavvKjQ1SsRWteB8o7lGe2PcG2h2v5yW2vk5rrmuB0ehogeJez3ynlk508HguRtidzxnKdZyc6SHOh2hbWq1clspJicREsHlz3Rfn7upOSUyFmx+Tilfnjuom5FFYGBNZt0aEjaW5S7fjcYJBTEerjGjTz3vUs2DK3C5ymyCKKasA628fVZ20uIZhmn5NUGGK6bKDusFOQhYjRnxRYwS2fToBHiVfC8wo7bWW8ZwOrbguvedJU2q9pwjKvXy9upw8Ra3EcYzXASqwI6rqUQv6htGqzTYtVkRsdVxaQqITc3FYrhKIroru4Iup4xYtcVojppz0+HQxiv5WtxosVtfXXX4Zj4spr9ThoZgzLe3ebILEIK3wJNmK/AxV80NXq7JOI6gaiLK/fbm7gd9G7oW9VuM50OJELDmR4jU2k1KSx6sD5lB+c+Ajp1iw+iS9ETD/je93+eyBRKGM9fqP0+DpNsJBBlVLuWK/BwidBN186pFi4DlMzo13Wd0zYKQce4NVrf5s4f7T01KeBU+nnvvcuI+rW7tsY3R18zgP9D9uX2E5066qJQdrFov2YuXwovS4hCQDttIfz9YoGUKKQ3PObWUVynhtBptoGbhRw=
url: AgCjVnAWNarZplxbAurz++eBiKXIuYUcoleDcsrMdwdflBOOf6ayguCr7CDHzUbuKFnlNKoFvIvjUOqFb1v5Yy4iWju3ajvlo+ncEIezxetYOmQSft3nSeOD3+RCZn4Qf4K2C1D113IyPCo7T2h01KuVLWOItfuWuVwFDpLTvmKfTRAs8eP1L4ohvAcY0/J5mAmi7tXV2hN1r9R2MEuvHOusujtrRqewXHhzBksSb9/wxilruU/BHQAhYKeKHzi7QoIOeXJTnGEYxuTvskwKQhschIOIBPAOLaUbgKkuHuDIf8y1Gv2b8ENu5uNvTb/ZD43jtmx9P4pS+Hwc7OW411TrkRO7XbV7qo/PqYGYpkKYDK4g7ONGnzrsmXbSmip34vXll/jAknY61QQ6D6JbbONw/psX72p+ZFvOedhlKbHRuUnDYXyQgKFWBODLb63RaMYai79qbv8mJcwDLJaPXYXpwLumHeZX91uPPjahxfvOe8VoryTpvHIbxJO85VJJ1q+7uyFy6h7LVLhifYSbb+M55p/e5Ds5gNcgUv6npPUHdbf0yjYbT1tXGjhaqk4Tx93WoOKPQ/j+nPB41akooY0YfL/ZTDzr2iCMByrx/uPQz3JE+m7VrH5BZyYjj9sSASVsULabJGFiSuGpD+u/lcUNJW+WHQetMEU7+wNo/WB3E5iG9J8qPSPFcTiTslEa8cvGPNtNgyCQ2PphlinscQsvcAQ+ALtEd1dWXPhIGbF8udK6Wx6NYt8=
template:
metadata:
creationTimestamp: null
labels:
argocd.argoproj.io/secret-type: repository
name: snothub-repo-creds
namespace: argocd
type: Opaque

View File

@@ -9,6 +9,7 @@
- [Updating an Existing Application](#updating-an-existing-application) - [Updating an Existing Application](#updating-an-existing-application)
- [Working with Secrets](#working-with-secrets) - [Working with Secrets](#working-with-secrets)
- [Enabling Authentication for Applications](#enabling-authentication-for-applications) - [Enabling Authentication for Applications](#enabling-authentication-for-applications)
- [Adding a New Keycloak Client](#adding-a-new-keycloak-client)
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
- [Best Practices](#best-practices) - [Best Practices](#best-practices)
@@ -89,21 +90,21 @@ If you do need cluster access, install:
You'll need read/write access to these repositories: You'll need read/write access to these repositories:
1. **sturdy-adventure** (Config repo) 1. **launchpad** (Config repo)
```bash ```bash
git clone https://github.com/fortedigital/sturdy-adventure.git git clone https://git.forteapps.net/Forte/launchpad.git
cd sturdy-adventure cd launchpad
``` ```
2. **helm-values** (Values repo) 2. **helm-values** (Values repo)
```bash ```bash
git clone git@github.com:fortedigital/helm-values.git git clone https://git.forteapps.net/Forte/helm-prod-values.git
cd helm-values cd helm-values
``` ```
3. **forte-helm** (Chart repo - read-only for most developers) 3. **forte-helm** (Chart repo - read-only for most developers)
```bash ```bash
git clone https://github.com/snothub/forte-helm.git git clone https://git.forteapps.net/Forte/forte-helm.git
cd forte-helm cd forte-helm
``` ```
@@ -132,9 +133,9 @@ mkdir -p ~/dev/k8s
cd ~/dev/k8s cd ~/dev/k8s
# Clone repositories # Clone repositories
git clone https://github.com/fortedigital/sturdy-adventure.git launchpad git clone https://git.forteapps.net/Forte/launchpad.git launchpad
git clone git@github.com:fortedigital/helm-values.git helm-prod-values git clone https://git.forteapps.net/Forte/helm-prod-values helm-prod-values
git clone https://github.com/snothub/forte-helm.git forte-helm git clone https://git.forteapps.net/Forte/forte-helm forte-helm
# Your folder structure: # Your folder structure:
# ~/dev/k8s/ # ~/dev/k8s/
@@ -201,7 +202,7 @@ Our setup uses three repositories:
|------------|---------|-----------|-----------| |------------|---------|-----------|-----------|
| **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely | | **forte-helm** | Helm chart templates (generic, reusable) | Platform engineers | ❌ Rarely |
| **helm-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes | | **helm-values** | Application configuration (image tag, env vars) | Developers / CI pipelines | ✅ Sometimes |
| **sturdy-adventure** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app | | **launchpad** | ArgoCD Applications (what gets deployed) | Platform / DevOps engineers | ✅ Per new app |
### Example: Deploying "myapp" ### Example: Deploying "myapp"
@@ -236,7 +237,7 @@ app:
value: https://api.example.com value: https://api.example.com
``` ```
#### Repository: `sturdy-adventure` (ArgoCD Application) #### Repository: `launchpad` (ArgoCD Application)
```yaml ```yaml
# apps/myapp.yaml # apps/myapp.yaml
# Tells ArgoCD to deploy your app # Tells ArgoCD to deploy your app
@@ -247,7 +248,7 @@ metadata:
namespace: argocd namespace: argocd
spec: spec:
sources: sources:
- repoURL: https://github.com/snothub/forte-helm - repoURL: https://github.com/fortedigital/forte-helm
path: forteapp path: forteapp
helm: helm:
valueFiles: valueFiles:
@@ -386,7 +387,7 @@ git push
### Step 3: Create ArgoCD Application Manifest ### Step 3: Create ArgoCD Application Manifest
In the `sturdy-adventure` repository, create `apps/hello-world.yaml`: In the `launchpad` repository, create `apps/hello-world.yaml`:
```yaml ```yaml
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
@@ -411,7 +412,7 @@ spec:
sources: sources:
# Source 1: Helm chart templates # Source 1: Helm chart templates
- repoURL: https://github.com/snothub/forte-helm - repoURL: https://github.com/fortedigital/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
@@ -1025,7 +1026,7 @@ policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
#### Sidecar Configuration #### Sidecar Configuration
The auth sidecar container: The auth sidecar container:
- **Image**: `ghcr.io/snothub/stunning-memory:latest` - **Image**: `ghcr.io/fortedigital/auth-sidecar:latest`
- **Port**: 8080 - **Port**: 8080
- **Resources**: 10m CPU / 32Mi memory (requests), 50m CPU / 64Mi memory (limits) - **Resources**: 10m CPU / 32Mi memory (requests), 50m CPU / 64Mi memory (limits)
- **Health checks**: `/healthz` endpoint - **Health checks**: `/healthz` endpoint
@@ -1247,6 +1248,202 @@ kubectl logs -n myapp <pod-name> -c authn
--- ---
## Adding a New Keycloak Client
There are two ways to add an OIDC client, depending on your use case:
| Method | Best for | Who edits the infra repo? |
|--------|----------|--------------------------|
| **Self-service** (recommended) | New apps that deploy their own resources | App developer — no infra changes needed |
| **Legacy (realm JSON)** | Existing clients already defined in forte-realm.json (e.g., Gitea) | Platform engineer |
Both methods are served by the **Keycloak Client Registrar** CronJob, which runs every 2 minutes.
### Self-Service OIDC Client Registration
This is the recommended flow for new applications. Your app deploys a labeled config Secret in its own namespace; the platform handles everything else.
#### How It Works
1. You deploy a Secret with label `keycloak.forteapps.net/client-config: "true"` containing a `client.json` definition
2. A **Kyverno ClusterPolicy** (`keycloak-client-config-cloner`) clones it to the `keycloak` namespace
3. The **Client Registrar CronJob** picks it up within 2 minutes:
- Registers (or updates) the client in Keycloak
- Fetches the auto-generated client secret
- Creates a credential Secret in your app's namespace
- Annotates the config Secret with sync status
#### Step 1: Create the Config Secret
Deploy this Secret in your application's namespace (e.g., as part of your Helm chart or Kustomize overlay):
```yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-myapp
namespace: myapp
labels:
keycloak.forteapps.net/client-config: "true"
stringData:
client.json: |
{
"clientId": "myapp",
"name": "My Application",
"redirectUris": ["https://myapp.forteapps.net/*"],
"webOrigins": ["https://myapp.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "myapp",
"name": "myapp-oidc-credentials",
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
}
}
```
**`client.json` fields**:
| Field | Required | Description |
|-------|----------|-------------|
| `clientId` | Yes | Keycloak client ID |
| `name` | Yes | Display name in Keycloak |
| `redirectUris` | Yes | Allowed redirect URIs |
| `webOrigins` | Yes | Allowed web origins (CORS) |
| `defaultClientScopes` | No | Scopes (default: `["openid", "email", "profile"]`) |
| `protocolMappers` | No | Custom claim mappers (default: `[]`) |
| `secret.namespace` | No | Namespace for the credential Secret (default: source namespace) |
| `secret.name` | No | Name of the credential Secret (default: `<clientId>-oidc-credentials`) |
| `secret.keys.clientId` | No | Key name for client ID in credential Secret (default: `client-id`) |
| `secret.keys.clientSecret` | No | Key name for client secret in credential Secret (default: `client-secret`) |
#### Step 2: Reference the Credential Secret
In your application's deployment config, reference the credential Secret that the registrar creates:
```yaml
env:
- name: OIDC_CLIENT_ID
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-id
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: myapp-oidc-credentials
key: client-secret
```
#### Step 3: Deploy and Wait
Commit and push your changes. The credential Secret will appear within 2 minutes:
```bash
# Watch for the credential Secret to be created
kubectl get secret myapp-oidc-credentials -n myapp -w
# Check registrar logs
kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
# Check sync status on the config Secret
kubectl get secret keycloak-client-myapp -n keycloak -o jsonpath='{.metadata.annotations}'
```
#### Change Detection
The registrar computes a SHA-256 hash of `client.json` and stores it as an annotation. On subsequent runs, it skips processing if:
- The hash hasn't changed, AND
- The credential Secret already exists in the target namespace
To force a re-sync, update any field in `client.json` (e.g., add a trailing space to `name`).
### Legacy Method: Realm JSON
Existing clients (like Gitea) are defined directly in `forte-realm.json` inside `keycloak-values.yaml`. The registrar syncs their secrets via client attributes.
#### Step 1: Add Client to Realm Config
In `infra/values/base/keycloak-values.yaml`, add a new entry to the `clients` array in `forte-realm.json`:
```json
{
"clientId": "myapp",
"name": "My Application",
"enabled": true,
"protocol": "openid-connect",
"clientAuthenticatorType": "client-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"publicClient": false,
"redirectUris": ["https://myapp.forteapps.net/*"],
"webOrigins": ["https://myapp.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"attributes": {
"k8s.secret.sync": "true",
"k8s.secret.namespace": "myapp",
"k8s.secret.name": "myapp-oidc-credentials",
"k8s.secret.client-id-key": "key",
"k8s.secret.client-secret-key": "secret"
}
}
```
**Important**:
- Do **NOT** include a `"secret"` field — Keycloak generates one automatically
- The `attributes` block tells the registrar where to create the K8s Secret
- Set `client-id-key` / `client-secret-key` to match what the consuming app expects (defaults: `client-id` / `client-secret`)
#### Step 2: Reference the Secret in Your Application
```yaml
existingSecret: myapp-oidc-credentials
```
#### Step 3: Commit and Push
```bash
cd ~/dev/k8s/launchpad
git add infra/values/base/keycloak-values.yaml
git commit -m "Add myapp Keycloak client with auto-sync"
git push
```
ArgoCD will sync the Keycloak config, and the registrar CronJob will pick up the new client within 2 minutes.
#### Legacy Sync Attribute Reference
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `k8s.secret.sync` | Yes | — | Set to `"true"` to enable syncing |
| `k8s.secret.namespace` | Yes | — | Target K8s namespace for the secret |
| `k8s.secret.name` | Yes | — | Name of the K8s Secret to create |
| `k8s.secret.client-id-key` | No | `client-id` | Field name for the client ID in the K8s Secret |
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for the client secret in the K8s Secret |
### Retrieving Secrets for External Deployments
The registrar always writes a **central copy** of every synced secret to the `secrets` namespace, in addition to the target namespace. This allows operators to retrieve client credentials for applications deployed outside this cluster:
```bash
# View the central copy
kubectl get secret gitea-oidc-credentials -n secrets -o yaml
# Extract the client secret for use elsewhere
kubectl get secret myapp-oidc-credentials -n secrets \
-o jsonpath='{.data.client-secret}' | base64 -d
```
### Registrar Behavior Notes
- The registrar runs as a CronJob every 2 minutes (`concurrencyPolicy: Forbid`)
- If the target namespace doesn't exist, the target write is skipped with a warning (the central copy still happens)
- A central copy is **always** written to the `secrets` namespace for every synced client
- The registrar uses the `keycloak-credentials` secret for admin authentication
- Created secrets have the label `app.kubernetes.io/managed-by: keycloak-client-registrar`
---
## Troubleshooting ## Troubleshooting
### Application Not Deploying ### Application Not Deploying
@@ -1579,4 +1776,4 @@ Now that you understand the basics:
- Docs: [Full documentation index](README.md) - Docs: [Full documentation index](README.md)
- Help: Contact platform team - Help: Contact platform team
**Last Updated**: 2026-03-16 **Last Updated**: 2026-04-16

View File

@@ -16,12 +16,12 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
### Key Characteristics ### Key Characteristics
- **Environment**: Production (internal use only) - **Environment**: Production (internal use only)
- **Cluster Type**: Single cluster, single environment - **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)
- **Ingress**: Traefik with Let's Encrypt TLS - **Ingress**: Traefik with Let's Encrypt TLS
- **Monitoring**: Prometheus + Grafana + Loki + Fluent-Bit - **Monitoring**: Prometheus + Grafana + Loki + Tempo + Fluent-Bit
- **Policy Engine**: Kyverno - **Policy Engine**: Kyverno
- **Notifications**: Slack integration for sync status - **Notifications**: Slack integration for sync status
@@ -53,8 +53,8 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Config Repository │ │ Config Repository │
│ (ArgoCD Applications) │ │ (ArgoCD Applications) │
│ github.com/snothub/ │ git.forteapps.net/Forte/
sturdy-adventure launchpad
└────────────────────────────────┘ └────────────────────────────────┘
@@ -62,8 +62,8 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ Kubernetes Cluster │ Kubernetes Clusters
│ (UpCloud Managed) │ (UpCloud: upc-dev, upc-prod)
│ │ │ │
│ ┌──────────────────────────┐ │ │ ┌──────────────────────────┐ │
│ │ ArgoCD │ │ │ │ ArgoCD │ │
@@ -83,6 +83,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
│ │ - Prometheus │ │ │ │ - Prometheus │ │
│ │ - Grafana │ │ │ │ - Grafana │ │
│ │ - Loki │ │ │ │ - Loki │ │
│ │ - Tempo │ │
│ │ - Fluent-Bit │ │ │ │ - Fluent-Bit │ │
│ └──────────────────────────┘ │ │ └──────────────────────────┘ │
│ │ │ │
@@ -108,87 +109,82 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
## Repository Structure ## Repository Structure
### 1. **Config Repository** (Current Repo) ### 1. **Config Repository** (Current Repo)
**Repository**: `https://github.com/fortedigital/sturdy-adventure.git` **Repository**: `https://git.forteapps.net/Forte/launchpad`
**Purpose**: GitOps configuration - ArgoCD Applications and cluster resources **Purpose**: GitOps configuration - ArgoCD Applications and cluster resources
**Location**: `C:\dev\k8s\launchpad` **Location**: `C:\dev\k8s\launchpad`
``` ```
sturdy-adventure/ launchpad/
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern) ├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev cluster)
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod cluster)
├── infra/ # Infrastructure ArgoCD Applications ├── infra/ # Infrastructure ArgoCD Applications (Kustomize)
│ ├── enterprise-apps.yaml # Parent app managing all apps in apps/ │ ├── base/ # Base Application manifests (upc-dev defaults)
│ ├── cluster-resources-application.yaml │ ├── kustomization.yaml
│ ├── traefik-application.yaml │ ├── traefik-application.yaml
│ ├── cert-manager-application.yaml │ ├── keycloak.yaml
│ ├── kyverno.yaml │ ├── grafana.yaml
│ ├── kyverno-policies.yaml │ ├── gitea.yaml
│ ├── prometheus.yaml │ ├── gitea-actions.yaml
│ ├── grafana.yaml │ ├── tempo.yaml
│ ├── loki.yaml │ ├── renovate.yaml
│ ├── fluent-bit.yaml │ ├── ... # All other Application manifests
├── trivy.yaml │ └── secrets.yaml
│ ├── sealedsecrets.yaml │ ├── overlays/ # Per-cluster overrides
│ ├── secrets.yaml │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
│ │ └── upc-prod/ # UpCloud Prod (patches value paths)
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides for infra │ └── values/ # Helm value overrides for infra
│ ├── argocd-values.yaml │ ├── base/ # Shared values (all clusters)
├── prometheus-values.yaml │ ├── traefik-values.yaml
├── grafana-values.yaml │ ├── keycloak-values.yaml
├── loki-values.yaml │ ├── grafana-values.yaml
└── fluent-bit-values.yaml │ ├── prometheus-values.yaml
│ │ ├── gitea-values.yaml
│ │ └── ...
│ ├── upc-dev/ # upc-dev cluster-specific values
│ │ ├── traefik-values.yaml
│ │ ├── keycloak-values.yaml
│ │ └── grafana-values.yaml
│ └── upc-prod/ # upc-prod cluster-specific values
│ ├── traefik-values.yaml
│ ├── keycloak-values.yaml
│ └── grafana-values.yaml
├── apps/ # Business Application ArgoCD manifests ├── apps/ # Business Application ArgoCD manifests (Kustomize)
│ ├── mcp10x.yaml # MCP 10X application │ ├── base/ # Base app manifests
│ ├── musicman.yaml # Music Man application │ ├── kustomization.yaml
│ ├── dot-ai-stack.yaml # Dot AI Stack │ ├── dot-ai-stack.yaml
│ └── argo-mcp.yaml # ArgoCD MCP server │ └── ...
│ └── overlays/
│ ├── upc-dev/ # Uses base as-is
│ └── upc-prod/ # Patches value paths
├── cluster-resources/ # Cluster-wide Kubernetes resources ├── cluster-resources/ # Cluster-wide Kubernetes resources
│ ├── cert-manager-namespace.yaml │ ├── ...
│ ├── secrets-namespace.yaml
│ ├── letsencrypt-issuer.yaml # Let's Encrypt ClusterIssuer
│ ├── kyverno-config.yaml
│ ├── argocd-notifications-secret-sealed.yaml
│ ├── snothub-repo-credentials-sealed.yaml
│ ├── forte10x-repo-credentials-sealed.yaml
│ ├── mcp10x-repo-credentials-sealed.yaml
│ └── policies/ # Kyverno policies │ └── policies/ # Kyverno policies
│ ├── deployment-verifier.yaml
│ ├── label-checker.yaml
│ ├── bare-pod-cleaner.yaml
│ ├── replicaset-cleaner.yaml
│ ├── default-ns-blocker.yaml
│ ├── secret-cloner.yaml
│ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed) ├── secrets/ # Application secrets (sealed, per-cluster)
── argocd-mcp-credentials.yaml ── upc-dev/ # Secrets for upc-dev cluster
│ ├── dot-ai-secrets.yaml
│ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.yaml
├── private/ # Local-only files (NOT in Git) ├── private/ # Local-only files (NOT in Git)
│ ├── *.yaml # Unsealed secrets
│ └── *.sh # Helper scripts
└── docs/ # Documentation └── docs/ # Documentation
├── GITOPS-ARCHITECTURE.md # This file
├── DEVELOPER-GUIDE.md
├── OPERATIONS-RUNBOOK.md
└── REFERENCE.md
``` ```
**Key Points**: **Key Points**:
- `_app-of-apps.yaml` is the root Application that ArgoCD monitors - `_app-of-apps-upc-dev.yaml` and `_app-of-apps-upc-prod.yaml` are the per-cluster root Applications
- `infra/enterprise-apps.yaml` auto-discovers all apps in `apps/` folder - Kustomize overlays in `infra/overlays/` render base Applications with per-cluster patches
- Helm values are split: `values/base/` (shared) + `values/upc-dev/` or `values/upc-prod/` (cluster-specific)
- `apps/` follows the same base/overlays pattern for business applications
- Changes pushed to this repo trigger automatic syncs in ArgoCD - Changes pushed to this repo trigger automatic syncs in ArgoCD
- `private/` folder contains local-only files (Git-ignored) - `private/` folder contains local-only files (Git-ignored)
--- ---
### 2. **Helm Charts Repository** ### 2. **Helm Charts Repository**
**Repository**: `https://github.com/snothub/forte-helm` **Repository**: `https://github.com/fortedigital/forte-helm`
**Purpose**: Reusable Helm chart templates for Forte applications **Purpose**: Reusable Helm chart templates for Forte applications
**Location**: `C:\dev\k8s\forte-helm` **Location**: `C:\dev\k8s\forte-helm`
@@ -293,7 +289,7 @@ app-repository/
### The App-of-Apps Pattern ### The App-of-Apps Pattern
``` ```
_app-of-apps.yaml (Root) _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
@@ -302,6 +298,7 @@ _app-of-apps.yaml (Root)
│ ├── kyverno │ ├── kyverno
│ ├── prometheus │ ├── prometheus
│ ├── grafana │ ├── grafana
│ ├── tempo
│ └── ... (other infra apps) │ └── ... (other infra apps)
└── enterprise-apps (manages apps/) └── enterprise-apps (manages apps/)
@@ -312,10 +309,10 @@ _app-of-apps.yaml (Root)
``` ```
**How It Works**: **How It Works**:
1. Bootstrap script installs ArgoCD and applies `_app-of-apps.yaml` 1. Bootstrap script installs ArgoCD and applies `_app-of-apps-upc-dev.yaml` (or `upc-prod`)
2. ArgoCD creates the root Application which monitors `infra/` folder 2. ArgoCD creates the root Application which monitors the appropriate `infra/overlays/` folder
3. Each YAML in `infra/` becomes a child Application 3. Kustomize renders base Applications with cluster-specific patches
4. `enterprise-apps.yaml` monitors `apps/` folder and auto-discovers applications 4. `enterprise-apps` Application monitors the cluster's `apps/overlays/` folder
5. ArgoCD continuously syncs (every 60s) and auto-heals drift 5. ArgoCD continuously syncs (every 60s) and auto-heals drift
### Sync Waves & Ordering ### Sync Waves & Ordering
@@ -343,7 +340,7 @@ Applications like `mcp10x` and `musicman` use multiple sources:
```yaml ```yaml
spec: spec:
sources: sources:
- repoURL: https://github.com/snothub/forte-helm - repoURL: https://github.com/fortedigital/forte-helm
path: forteapp # Helm chart templates path: forteapp # Helm chart templates
helm: helm:
valueFiles: valueFiles:
@@ -360,6 +357,34 @@ spec:
- Easy to update all apps by changing the chart - Easy to update all apps by changing the chart
- Environment-specific values isolated in separate repo - Environment-specific values isolated in separate repo
### Multi-Cluster Pattern
Kustomize overlays enable deploying the same Applications across clusters with different configurations:
```yaml
# infra/base/ contains default (upc-dev) Applications
# Helm values are layered: base + cluster-specific
valueFiles:
- $values/infra/values/base/traefik-values.yaml # Shared config
- $values/infra/values/upc-dev/traefik-values.yaml # Cluster-specific
# infra/overlays/upc-prod/kustomization.yaml patches the second valueFile
patches:
- target:
kind: Application
name: traefik
patch: |
- op: replace
path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/upc-prod/traefik-values.yaml
```
**Benefits**:
- Single source of truth for Application definitions
- Cluster-specific values isolated per overlay
- Easy to add new clusters by creating a new overlay
- Base values shared across all clusters reduce duplication
--- ---
## CI/CD Pipeline ## CI/CD Pipeline
@@ -402,7 +427,7 @@ jobs:
1. **Config Repo Change**: 1. **Config Repo Change**:
- Developer updates `apps/myapp.yaml` - Developer updates `apps/myapp.yaml`
- Pushes to `sturdy-adventure` repo - Pushes to `launchpad` repo
- ArgoCD detects change (60s reconciliation) - ArgoCD detects change (60s reconciliation)
- Syncs application to cluster - Syncs application to cluster
@@ -489,7 +514,6 @@ git commit -m "Add app credentials"
**Private Repository Credentials** stored as SealedSecrets: **Private Repository Credentials** stored as SealedSecrets:
```yaml ```yaml
# cluster-resources/snothub-repo-credentials-sealed.yaml
# cluster-resources/forte10x-repo-credentials-sealed.yaml # cluster-resources/forte10x-repo-credentials-sealed.yaml
``` ```
@@ -528,8 +552,9 @@ annotations:
1. **Prometheus**: Metrics collection and storage 1. **Prometheus**: Metrics collection and storage
2. **Grafana**: Metrics visualization and dashboards 2. **Grafana**: Metrics visualization and dashboards
3. **Loki**: Log aggregation 3. **Loki**: Log aggregation
4. **Fluent-Bit**: Log shipping from pods to Loki 4. **Tempo**: Distributed tracing (OTLP)
5. **Trivy**: Container vulnerability scanning 5. **Fluent-Bit**: Log shipping from pods to Loki
6. **Trivy**: Container vulnerability scanning
### Slack Notifications ### Slack Notifications
@@ -558,7 +583,7 @@ Notifications include:
**Rebuild Process**: **Rebuild Process**:
1. Provision new Kubernetes cluster 1. Provision new Kubernetes cluster
2. Clone `sturdy-adventure` repository 2. Clone `launchpad` repository
3. Run `./bootstrap.sh` 3. Run `./bootstrap.sh`
4. ArgoCD installs and syncs all applications 4. ArgoCD installs and syncs all applications
5. Manually recreate unsealed secrets and seal them 5. Manually recreate unsealed secrets and seal them

View File

@@ -51,8 +51,8 @@ kubectl get nodes
```bash ```bash
# 1. Clone config repository # 1. Clone config repository
git clone https://github.com/fortedigital/sturdy-adventure.git git clone https://git.forteapps.net/Forte/launchpad
cd sturdy-adventure cd launchpad
# 2. Set cluster name (optional) # 2. Set cluster name (optional)
export CLUSTER_NAME="prod-cluster-01" export CLUSTER_NAME="prod-cluster-01"
@@ -130,10 +130,10 @@ Generate a dedicated SSH key for ArgoCD without a passphrase (required for autom
```bash ```bash
# Generate ED25519 key (recommended - smaller and more secure) # Generate ED25519 key (recommended - smaller and more secure)
ssh-keygen -t ed25519 -C "argocd-deploy-key-sturdy-adventure" -f argocd-deploy-key -N "" ssh-keygen -t ed25519 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key -N ""
# Or RSA key if ED25519 is not supported # Or RSA key if ED25519 is not supported
ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-sturdy-adventure" -f argocd-deploy-key -N "" ssh-keygen -t rsa -b 4096 -C "argocd-deploy-key-launchpad" -f argocd-deploy-key -N ""
``` ```
This creates two files: This creates two files:
@@ -148,7 +148,7 @@ This creates two files:
``` ```
2. Go to GitHub repository settings: 2. Go to GitHub repository settings:
- Navigate to: `https://github.com/fortedigital/sturdy-adventure/settings/keys` - Navigate to: `https://git.forteapps.net/Forte/launchpad/settings/keys`
- Or: Repository → Settings → Deploy keys - Or: Repository → Settings → Deploy keys
3. Click **"Add deploy key"** 3. Click **"Add deploy key"**
@@ -169,78 +169,33 @@ This creates two files:
Add the private key to ArgoCD as a repository secret: Add the private key to ArgoCD as a repository secret:
Save the following file in private/ (gitignored) folder as secret.yaml
```bash ```bash
# Create secret for sturdy-adventure repository apiVersion: v1
kubectl create secret generic repo-sturdy-adventure \ kind: Secret
--from-file=sshPrivateKey=argocd-deploy-key \ metadata:
--namespace=argocd \ name: forte-helm-repo
--dry-run=client -o yaml | kubectl apply -f - namespace: argocd
labels:
# Label it for ArgoCD to recognize argocd.argoproj.io/secret-type: repository
kubectl label secret repo-sturdy-adventure \ stringData:
-n argocd \ type: git
argocd.argoproj.io/secret-type=repository url: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
sshPrivateKey: |
# Add repository annotations <paste your private key here>
kubectl annotate secret repo-sturdy-adventure \ project: default
-n argocd \
managed-by=argocd.argoproj.io
``` ```
Seal the secret using `kubeseal` command
Alternatively, create a complete repository secret with all metadata:
```bash ```bash
kubectl apply -f - <<EOF kubeseal --format=yaml \
apiVersion: v1 --namespace=argocd \
kind: Secret < private/secret.yaml \
metadata: > secrets/forte-helm-repo-secret-sealed.yaml
name: repo-sturdy-adventure
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
annotations:
managed-by: argocd.argoproj.io
type: Opaque
stringData:
type: git
url: git@github.com:fortedigital/sturdy-adventure.git
sshPrivateKey: |
$(cat argocd-deploy-key | sed 's/^/ /')
EOF
``` ```
**Step 4: Register Repository in ArgoCD** **Step 4: Register Repository in ArgoCD**
Add the repository to ArgoCD's configuration: Check in secrets/forte-helm-repo-secret-sealed.yaml and let Argo sync and create the secret.
```bash
# Via kubectl (recommended for GitOps)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: repo-sturdy-adventure
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
type: Opaque
stringData:
type: git
url: git@github.com:fortedigital/sturdy-adventure.git
sshPrivateKey: |
$(cat argocd-deploy-key | sed 's/^/ /')
insecure: "false"
enableLfs: "false"
EOF
# Or via ArgoCD UI
# 1. Open ArgoCD UI: kubectl port-forward svc/argocd-server -n argocd 8080:443
# 2. Navigate to: Settings → Repositories → Connect Repo
# 3. Connection Method: Via SSH
# 4. Repository URL: git@github.com:fortedigital/sturdy-adventure.git
# 5. SSH private key: Paste private key content
# 6. Click "Connect"
```
**Step 5: Verify Repository Access** **Step 5: Verify Repository Access**
@@ -252,7 +207,7 @@ kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository
# Settings → Repositories → Should show "Successful" status # Settings → Repositories → Should show "Successful" status
# Test by creating an application # Test by creating an application
kubectl apply -f _app-of-apps.yaml kubectl apply -f _app-of-apps-upc-dev.yaml # or _app-of-apps-upc-prod.yaml
# Check application sync status # Check application sync status
kubectl get applications -n argocd kubectl get applications -n argocd
@@ -272,7 +227,7 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: main targetRevision: main
path: cluster-resources path: cluster-resources
destination: destination:
@@ -318,7 +273,7 @@ rm /tmp/test-repo-access.yaml
# Add new public key to GitHub (keep old key for now) # Add new public key to GitHub (keep old key for now)
# Update Kubernetes secret # Update Kubernetes secret
kubectl create secret generic repo-sturdy-adventure \ kubectl create secret generic repo-launchpad \
--from-file=sshPrivateKey=argocd-new-key \ --from-file=sshPrivateKey=argocd-new-key \
--namespace=argocd \ --namespace=argocd \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
@@ -335,7 +290,7 @@ rm /tmp/test-repo-access.yaml
kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository kubectl get secrets -n argocd -l argocd.argoproj.io/secret-type=repository
# Review deploy keys in GitHub # Review deploy keys in GitHub
# Visit: https://github.com/fortedigital/sturdy-adventure/settings/keys # Visit: https://git.forteapps.net/Forte/launchpad/settings/keys
``` ```
4. **Use Different Keys per Repository** 4. **Use Different Keys per Repository**
@@ -349,16 +304,16 @@ rm /tmp/test-repo-access.yaml
```bash ```bash
# Check if secret exists # Check if secret exists
kubectl get secret repo-sturdy-adventure -n argocd kubectl get secret repo-launchpad -n argocd
# Verify secret has correct label # Verify secret has correct label
kubectl get secret repo-sturdy-adventure -n argocd -o yaml | grep argocd.argoproj.io/secret-type kubectl get secret repo-launchpad -n argocd -o yaml | grep argocd.argoproj.io/secret-type
# Check ArgoCD application controller logs # Check ArgoCD application controller logs
kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied" kubectl logs -n argocd deployment/argocd-application-controller | grep -i "permission denied"
# Verify deploy key is added to GitHub # Verify deploy key is added to GitHub
# Visit: https://github.com/fortedigital/sturdy-adventure/settings/keys # Visit: https://git.forteapps.net/Forte/launchpad/settings/keys
``` ```
**Issue: "Host key verification failed"** **Issue: "Host key verification failed"**
@@ -369,7 +324,7 @@ kubectl exec -n argocd deployment/argocd-repo-server -- \
ssh-keyscan github.com >> ~/.ssh/known_hosts ssh-keyscan github.com >> ~/.ssh/known_hosts
# Or disable strict host key checking (less secure) # Or disable strict host key checking (less secure)
kubectl patch secret repo-sturdy-adventure -n argocd \ kubectl patch secret repo-launchpad -n argocd \
--type merge \ --type merge \
-p '{"stringData":{"insecure":"true"}}' -p '{"stringData":{"insecure":"true"}}'
``` ```
@@ -381,7 +336,7 @@ kubectl patch secret repo-sturdy-adventure -n argocd \
kubectl logs -n argocd deployment/argocd-repo-server kubectl logs -n argocd deployment/argocd-repo-server
# Refresh repository connection # Refresh repository connection
kubectl delete secret repo-sturdy-adventure -n argocd kubectl delete secret repo-launchpad -n argocd
# Recreate secret (see Step 3 above) # Recreate secret (see Step 3 above)
# Restart ArgoCD components # Restart ArgoCD components
@@ -391,21 +346,21 @@ kubectl rollout restart deployment argocd-application-controller -n argocd
#### Multiple Repository Setup #### Multiple Repository Setup
For the three-repository pattern (sturdy-adventure, forte-helm, helm-values): For the three-repository pattern (launchpad, forte-helm, helm-values):
```bash ```bash
# 1. sturdy-adventure (main config repo) # 1. launchpad (main config repo)
ssh-keygen -t ed25519 -C "argocd-sturdy-adventure" -f key-sturdy -N "" ssh-keygen -t ed25519 -C "argocd-launchpad" -f key-sturdy -N ""
# Add key-sturdy.pub to: https://github.com/fortedigital/sturdy-adventure/settings/keys # Add key-sturdy.pub to: https://git.forteapps.net/Forte/launchpad/settings/keys
# 2. helm-values (private values repo) # 2. helm-values (private values repo)
ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N "" ssh-keygen -t ed25519 -C "argocd-helm-values" -f key-helm-values -N ""
# Add key-helm-values.pub to: https://github.com/fortedigital/helm-values/settings/keys # Add key-helm-values.pub to: https://github.com/fortedigital/helm-values/settings/keys
# 3. forte-helm is public - no key needed (use HTTPS) # 3. forte-helm (private helm charts repo)
# Create secrets # Create secrets
kubectl create secret generic repo-sturdy-adventure \ kubectl create secret generic repo-launchpad \
--from-file=sshPrivateKey=key-sturdy \ --from-file=sshPrivateKey=key-sturdy \
--namespace=argocd --dry-run=client -o yaml | \ --namespace=argocd --dry-run=client -o yaml | \
kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \ kubectl label --local -f - argocd.argoproj.io/secret-type=repository --dry-run=client -o yaml | \
@@ -430,9 +385,9 @@ If you're currently using HTTPS and want to switch to SSH:
# 2. Update all Application manifests # 2. Update all Application manifests
# Change from: # Change from:
# repoURL: https://github.com/fortedigital/sturdy-adventure.git # repoURL: https://git.forteapps.net/Forte/launchpad
# To: # To:
# repoURL: git@github.com:fortedigital/sturdy-adventure.git # repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
# 3. Update and commit # 3. Update and commit
find . -name "*.yaml" -type f -exec sed -i 's|https://github.com/fortedigital/|git@github.com:fortedigital/|g' {} + find . -name "*.yaml" -type f -exec sed -i 's|https://github.com/fortedigital/|git@github.com:fortedigital/|g' {} +
@@ -999,6 +954,33 @@ curl -G -s 'http://localhost:3100/loki/api/v1/query_range' \
--data-urlencode 'start=1h' | jq --data-urlencode 'start=1h' | jq
``` ```
### Tempo Traces
```bash
# Port forward to Tempo query API
kubectl port-forward -n monitoring svc/tempo 3200:3200
# Access: http://localhost:3200
```
**Query traces via Grafana:**
1. Open Grafana → Explore
2. Select Tempo datasource
3. Use TraceQL or search by service name
**Verify Traefik is sending traces:**
```bash
# Check Traefik logs for OTLP export errors
kubectl logs -n traefik-system -l app.kubernetes.io/name=traefik | grep -i "traces export"
# Check Tempo is receiving data
kubectl logs -n monitoring -l app.kubernetes.io/name=tempo | grep "receiver"
```
**Trace-to-log correlation:**
- Click a trace span in Grafana → linked Loki logs appear (by namespace, pod, container)
- Trace-to-metrics links to Prometheus by service name
### Fluent-Bit Log Shipping ### Fluent-Bit Log Shipping
Verify Fluent-Bit is shipping logs: Verify Fluent-Bit is shipping logs:
@@ -1370,13 +1352,13 @@ kubectl get deployment argocd-server -n argocd \
-o jsonpath='{.spec.template.spec.containers[0].image}' -o jsonpath='{.spec.template.spec.containers[0].image}'
# Update version in values # Update version in values
vim infra/values/argocd-values.yaml vim infra/values/base/argocd-values.yaml
# Or upgrade via Helm directly # Or upgrade via Helm directly
helm upgrade argocd argo-cd \ helm upgrade argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--namespace argocd \ --namespace argocd \
--values infra/values/argocd-values.yaml \ --values infra/values/base/argocd-values.yaml \
--version 6.0.0 # New version --version 6.0.0 # New version
# Verify # Verify
@@ -1472,8 +1454,8 @@ kubectl top pods --all-namespaces --sort-by=cpu
Example: Adding Redis Example: Adding Redis
```bash ```bash
# 1. Create application manifest # 1. Create application manifest in base/
cat > infra/redis-application.yaml <<EOF cat > infra/base/redis-application.yaml <<EOF
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
metadata: metadata:
@@ -1483,15 +1465,17 @@ metadata:
argocd.argoproj.io/sync-wave: "1" argocd.argoproj.io/sync-wave: "1"
spec: spec:
project: default project: default
source: sources:
repoURL: https://charts.bitnami.com/bitnami - repoURL: https://charts.bitnami.com/bitnami
chart: redis chart: redis
targetRevision: 18.0.0 targetRevision: 18.0.0
helm: helm:
values: | releaseName: redis
auth: valueFiles:
enabled: true - \$values/infra/values/base/redis-values.yaml
password: changeme - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: redis namespace: redis
@@ -1503,30 +1487,37 @@ spec:
- CreateNamespace=true - CreateNamespace=true
EOF EOF
# 2. Commit and push # 2. Add to base kustomization
git add infra/redis-application.yaml # Edit infra/base/kustomization.yaml and add: - redis-application.yaml
# 3. Create base values file
cat > infra/values/base/redis-values.yaml <<EOF
auth:
enabled: true
EOF
# 4. Commit and push
git add infra/base/redis-application.yaml infra/values/base/redis-values.yaml infra/base/kustomization.yaml
git commit -m "Add Redis infrastructure component" git commit -m "Add Redis infrastructure component"
git push git push
# 3. ArgoCD will auto-sync within 60 seconds # 5. ArgoCD will auto-sync within 60 seconds
``` ```
### Multi-Cluster Setup (Future) ### Multi-Cluster Setup
For multi-cluster deployments: The repository supports multiple clusters via Kustomize overlays:
```yaml - **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is
# Different destinations per environment - **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod`
# dev-cluster
destination:
server: https://dev.k8s.example.com
namespace: myapp
# prod-cluster Each cluster has its own:
destination: - Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml`
server: https://prod.k8s.example.com - Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/`
namespace: myapp - Sealed secrets: `secrets/upc-dev/` (others as needed)
``` - Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/`
To add a new cluster, create a new overlay directory (e.g., `infra/overlays/upc-staging/`) with patches that swap the value file paths.
### Blue-Green Deployments ### Blue-Green Deployments

View File

@@ -180,7 +180,7 @@ Reference for:
┌──────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
Kubernetes Cluster (UpCloud) │ 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**: UpCloud Managed Kubernetes - **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

View File

@@ -21,7 +21,7 @@
|-----------|-------| |-----------|-------|
| **Provider** | UpCloud Managed Kubernetes | | **Provider** | UpCloud Managed Kubernetes |
| **Environment** | Production (internal use) | | **Environment** | Production (internal use) |
| **Cluster Count** | Single cluster | | **Cluster Count** | Multi-cluster (upc-dev, upc-prod) |
| **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 |
@@ -29,7 +29,9 @@
| **Secret Management** | Sealed Secrets (Bitnami) | | **Secret Management** | Sealed Secrets (Bitnami) |
| **Monitoring** | Prometheus + Grafana | | **Monitoring** | Prometheus + Grafana |
| **Logging** | Loki + Fluent-Bit | | **Logging** | Loki + Fluent-Bit |
| **Tracing** | Tempo (OTLP) |
| **Container Scanning** | Trivy | | **Container Scanning** | Trivy |
| **Version Control** | Gitea |
### Network Architecture ### Network Architecture
@@ -60,16 +62,17 @@ Internet
## Repository Reference ## Repository Reference
### Config Repository: `sturdy-adventure` ### Config Repository: `launchpad`
**URL**: `https://github.com/fortedigital/sturdy-adventure.git` **URL**: `https://git.forteapps.net/Forte/launchpad`
#### Directory Structure #### Directory Structure
``` ```
sturdy-adventure/ launchpad/
├── bootstrap.sh # Cluster initialization script ├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application ├── _app-of-apps-upc-dev.yaml # Root ArgoCD Application (upc-dev)
├── _app-of-apps-upc-prod.yaml # Root ArgoCD Application (upc-prod)
├── infra/ # Infrastructure applications ├── infra/ # Infrastructure applications
│ ├── cluster-resources-application.yaml │ ├── cluster-resources-application.yaml
@@ -81,16 +84,24 @@ sturdy-adventure/
│ ├── prometheus.yaml │ ├── prometheus.yaml
│ ├── grafana.yaml │ ├── grafana.yaml
│ ├── loki.yaml │ ├── loki.yaml
│ ├── tempo.yaml
│ ├── fluent-bit.yaml │ ├── fluent-bit.yaml
│ ├── trivy.yaml │ ├── trivy.yaml
│ ├── gitea.yaml
│ ├── gitea-actions.yaml
│ ├── sealedsecrets.yaml │ ├── sealedsecrets.yaml
│ ├── secrets.yaml │ ├── secrets.yaml
│ ├── renovate.yaml
│ └── values/ │ └── values/
│ ├── argocd-values.yaml │ ├── argocd-values.yaml
│ ├── prometheus-values.yaml │ ├── prometheus-values.yaml
│ ├── grafana-values.yaml │ ├── grafana-values.yaml
│ ├── loki-values.yaml │ ├── loki-values.yaml
── fluent-bit-values.yaml ── tempo-values.yaml
│ ├── gitea-values.yaml
│ ├── gitea-actions-values.yaml
│ ├── fluent-bit-values.yaml
│ └── renovate-values.yaml
├── apps/ # Business applications ├── apps/ # Business applications
│ ├── mcp10x.yaml │ ├── mcp10x.yaml
@@ -104,7 +115,6 @@ sturdy-adventure/
│ ├── letsencrypt-issuer.yaml │ ├── letsencrypt-issuer.yaml
│ ├── kyverno-config.yaml │ ├── kyverno-config.yaml
│ ├── argocd-notifications-secret-sealed.yaml │ ├── argocd-notifications-secret-sealed.yaml
│ ├── snothub-repo-credentials-sealed.yaml
│ ├── forte10x-repo-credentials-sealed.yaml │ ├── forte10x-repo-credentials-sealed.yaml
│ ├── mcp10x-repo-credentials-sealed.yaml │ ├── mcp10x-repo-credentials-sealed.yaml
│ └── policies/ │ └── policies/
@@ -114,11 +124,14 @@ sturdy-adventure/
│ ├── replicaset-cleaner.yaml │ ├── replicaset-cleaner.yaml
│ ├── default-ns-blocker.yaml │ ├── default-ns-blocker.yaml
│ ├── secret-cloner.yaml │ ├── secret-cloner.yaml
│ ├── keycloak-client-cloner.yaml
│ └── auth-sidecar-injector.yaml │ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed) ├── secrets/ # Application secrets (sealed)
│ ├── argocd-mcp-credentials.yaml │ ├── argocd-mcp-credentials.yaml
│ ├── dot-ai-secrets.yaml │ ├── dot-ai-secrets.yaml
│ ├── gitea-credentials-sealed.yaml
│ ├── gitea-runner-token-sealed.yaml
│ ├── mcp10x-credentials-sealed.yaml │ ├── mcp10x-credentials-sealed.yaml
│ └── musicman-credentials.yaml │ └── musicman-credentials.yaml
@@ -144,15 +157,15 @@ ArgoCd() {
helm upgrade --install argocd argo-cd \ helm upgrade --install argocd argo-cd \
--repo https://argoproj.github.io/argo-helm \ --repo https://argoproj.github.io/argo-helm \
--namespace argocd --create-namespace \ --namespace argocd --create-namespace \
--values infra/values/argocd-values.yaml \ --values infra/values/base/argocd-values.yaml \
--set notifications.context.clusterName="$CLUSTER_NAME" \ --set notifications.context.clusterName="$CLUSTER_NAME" \
--timeout 60s --atomic --timeout 60s --atomic
kubectl apply -f _app-of-apps.yaml -n argocd kubectl apply -f _app-of-apps-upc-dev.yaml -n argocd # or _app-of-apps-upc-prod.yaml
} }
``` ```
**`_app-of-apps.yaml`** **`_app-of-apps-upc-dev.yaml`** / **`_app-of-apps-upc-prod.yaml`**
```yaml ```yaml
apiVersion: argoproj.io/v1alpha1 apiVersion: argoproj.io/v1alpha1
kind: Application kind: Application
@@ -162,7 +175,7 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
path: infra path: infra
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
@@ -177,7 +190,7 @@ spec:
### Helm Charts Repository: `forte-helm` ### Helm Charts Repository: `forte-helm`
**URL**: `https://github.com/snothub/forte-helm` **URL**: `https://github.com/fortedigital/forte-helm`
#### Chart: `forteapp` #### Chart: `forteapp`
@@ -513,7 +526,7 @@ spec:
# Multi-source configuration # Multi-source configuration
sources: sources:
- repoURL: https://github.com/snothub/forte-helm - repoURL: https://github.com/fortedigital/forte-helm
path: forteapp path: forteapp
targetRevision: HEAD targetRevision: HEAD
helm: helm:
@@ -603,7 +616,7 @@ retry:
**Configuration**: **Configuration**:
```yaml ```yaml
# infra/traefik-application.yaml # infra/base/traefik-application.yaml
replicas: 2 replicas: 2
service: service:
@@ -704,6 +717,7 @@ kubeStateMetrics:
**Datasources**: **Datasources**:
- Prometheus - Prometheus
- Loki - Loki
- Tempo
### Loki ### Loki
@@ -721,6 +735,45 @@ promtail:
enabled: false # Using Fluent-Bit instead enabled: false # Using Fluent-Bit instead
``` ```
### Tempo
**Chart**: `grafana/tempo`
**Version**: 1.24.4
**Namespace**: `monitoring`
**Purpose**: Distributed tracing backend receiving OTLP traces from Traefik and other instrumented services.
**Configuration**:
```yaml
tempo:
storage:
trace:
backend: local
local:
path: /var/tempo/traces
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
persistence:
enabled: true
size: 10Gi
```
**Endpoints**:
- gRPC OTLP receiver: `:4317`
- HTTP OTLP receiver: `:4318`
- Query API: `:3200`
**Grafana Integration**:
- Trace-to-logs correlation with Loki (by namespace, pod, container)
- Trace-to-metrics correlation with Prometheus (by service name)
- Service graph and node graph visualization
### Fluent-Bit ### Fluent-Bit
**Chart**: `fluent/fluent-bit` **Chart**: `fluent/fluent-bit`
@@ -728,6 +781,263 @@ promtail:
**Output**: Loki **Output**: Loki
### Gitea
**Chart**: `gitea/gitea`
**Version**: 12.5.0 (app v1.25.4)
**Namespace**: `gitea`
**Purpose**: Self-hosted Git repository hosting with pull requests, issues, CI/CD (Gitea Actions), container registry, and package registry.
**Configuration**:
```yaml
# infra/base/gitea.yaml + infra/values/base/gitea-values.yaml
ingress:
host: git.forteapps.net
tls: cert-manager (letsencrypt-prod)
gitea:
admin:
existingSecret: gitea-credentials
config:
service:
DISABLE_REGISTRATION: true
ALLOW_ONLY_EXTERNAL_REGISTRATION: true
actions:
ENABLED: true
packages:
ENABLED: true
metrics:
ENABLED: true
postgresql:
enabled: true
persistence: 8Gi (upcloud-block-storage-maxiops)
```
**Authentication**: Keycloak OIDC via `forte` realm (client ID: `gitea`). Protocol mapper: `email_verified` hardcoded claim (`true`, boolean) on ID token, Access token, and Userinfo.
**Endpoints**:
- Web UI: `https://git.forteapps.net`
- SSH: port 22 (ClusterIP)
- Metrics: `/metrics` (Prometheus scrape)
**Secrets**: `gitea-credentials` (SealedSecret) containing `admin-password`, `postgres-password`, `secret` (OIDC client secret)
### Gitea Actions Runners
**Chart**: `actions` (from `https://dl.gitea.com/charts`)
**Namespace**: `gitea`
**Sync Wave**: 2 (deploys after Gitea)
**Purpose**: Act runners execute Gitea Actions CI/CD workflows. Deployed as a StatefulSet with a Docker-in-Docker sidecar for container-based job execution.
**Configuration**:
```yaml
# infra/base/gitea-actions.yaml + infra/values/base/gitea-actions-values.yaml
replicaCount: 3
runner:
labels:
- "ubuntu-latest:docker://node:20-bookworm"
- "ubuntu-22.04:docker://node:20-bookworm"
existingSecret: gitea-runner-token
gitea:
instance:
url: http://gitea-http.gitea.svc.cluster.local:3000
dind:
enabled: true # Docker-in-Docker sidecar (privileged)
```
**Resources**:
| Container | CPU Request | Memory Request | CPU Limit | Memory Limit |
|-----------|-------------|----------------|-----------|--------------|
| Runner | 250m | 256Mi | 1 | 1Gi |
| DinD sidecar | 250m | 256Mi | 1 | 1Gi |
**Secrets**: `gitea-runner-token` (SealedSecret) containing `token` (instance-level runner registration token from `/admin/runners`)
**Setup Steps**:
1. Get runner registration token from Gitea admin panel (`/admin/runners`)
2. Fill in `private/gitea-runner-token.yaml` with the token
3. Seal: `kubeseal --format yaml < private/gitea-runner-token.yaml > secrets/gitea-runner-token-sealed.yaml`
4. Commit and push — ArgoCD deploys runners automatically
**Verification**:
- `kubectl get statefulset -n gitea` — 3/3 runners ready
- Gitea admin panel (`/admin/runners`) — runners show as Online
- Create test workflow in `.gitea/workflows/test.yml` — job executes
### Keycloak Client Registrar
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
**Namespace**: `keycloak`
**Schedule**: `*/2 * * * *` (every 2 minutes)
**Purpose**: Handles two responsibilities:
1. **Legacy sync** — extracts secrets from Keycloak clients with `k8s.secret.sync: "true"` attribute (same as former PostSync syncer)
2. **Self-service registration** — processes config Secrets (cloned by Kyverno) to register new OIDC clients and sync their credentials
**How It Works**:
*Legacy path (existing clients like Gitea):*
1. Authenticates to Keycloak Admin API using admin credentials from `keycloak-credentials` secret
2. Queries all clients in the `forte` realm
3. Filters clients with `k8s.secret.sync: "true"` attribute
4. For each matching client, retrieves the auto-generated secret via Keycloak Admin API
5. Creates/updates a K8s Secret in the target namespace (from `k8s.secret.namespace` attribute)
6. Always writes a central copy to the `secrets` namespace
*Self-service path (new clients):*
1. Lists Secrets in `keycloak` namespace with label `keycloak.forteapps.net/client-config=true`
2. For each config Secret, parses `client.json` and computes a config hash
3. Skips if hash matches annotation and credential Secret already exists
4. Creates or updates the Keycloak client via Admin API
5. Fetches the generated client secret
6. Upserts credential Secret in target namespace + central `secrets` namespace
7. Annotates config Secret with sync status, config hash, and timestamp
**Resources**:
- `ServiceAccount`: `keycloak-client-registrar` (namespace: `keycloak`)
- `ClusterRole`: `keycloak-client-registrar` (secrets: get/list/create/update/patch; namespaces: get/list)
- `ClusterRoleBinding`: `keycloak-client-registrar`
- `CronJob`: `keycloak-client-registrar`
**Kyverno Policy**: `keycloak-client-config-cloner` — clones labeled Secrets from app namespaces to `keycloak` namespace (see [Kyverno Policies](#kyverno-policies))
**Legacy Client Attributes** (set in `forte-realm.json`):
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `k8s.secret.sync` | Yes | — | Set to `"true"` to enable syncing |
| `k8s.secret.namespace` | Yes | — | Target K8s namespace |
| `k8s.secret.name` | Yes | — | Name of the K8s Secret |
| `k8s.secret.client-id-key` | No | `client-id` | Field name for client ID in the Secret |
| `k8s.secret.client-secret-key` | No | `client-secret` | Field name for client secret in the Secret |
**Self-Service Config Secret Schema**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-<app>
namespace: <app-namespace>
labels:
keycloak.forteapps.net/client-config: "true"
stringData:
client.json: |
{
"clientId": "<app>",
"name": "<App Name>",
"redirectUris": ["https://<app>.forteapps.net/*"],
"webOrigins": ["https://<app>.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"protocolMappers": [],
"secret": {
"namespace": "<app-namespace>",
"name": "<app>-oidc-credentials",
"keys": { "clientId": "client-id", "clientSecret": "client-secret" }
}
}
```
**Created Credential Secret Format**:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: <target-name>
namespace: <target-namespace>
labels:
app.kubernetes.io/managed-by: keycloak-client-registrar
type: Opaque
data:
<client-id-key>: <base64-encoded client ID>
<client-secret-key>: <base64-encoded client secret>
```
**Config Secret Annotations** (set by registrar):
| Annotation | Description |
|-----------|-------------|
| `keycloak.forteapps.net/config-hash` | SHA-256 hash of client.json for change detection |
| `keycloak.forteapps.net/sync-status` | `synced` or `error` |
| `keycloak.forteapps.net/last-sync` | ISO 8601 timestamp of last successful sync |
**Verification**:
```bash
# Check CronJob status
kubectl get cronjobs -n keycloak
# View latest registrar logs
kubectl logs -n keycloak job/$(kubectl get jobs -n keycloak --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
# Verify created secret
kubectl get secret <name> -n <namespace> -o yaml
# Check config Secret annotations (self-service)
kubectl get secret keycloak-client-<app> -n keycloak -o jsonpath='{.metadata.annotations}'
```
**See**: [Developer Guide - Adding a New Keycloak Client](DEVELOPER-GUIDE.md#adding-a-new-keycloak-client)
### Renovate
**Chart**: `renovate` (OCI: `ghcr.io/renovatebot/charts`)
**Version**: 46.109.0 (app v43.113.0)
**Namespace**: `renovate`
**Sync Wave**: 2
**Purpose**: Automated dependency update bot. Runs as a CronJob that scans Gitea repositories for outdated dependencies and creates pull requests with updates.
**Configuration**:
```yaml
# infra/base/renovate.yaml + infra/values/base/renovate-values.yaml
cronjob:
schedule: "@daily"
concurrencyPolicy: Forbid
renovate:
config:
platform: gitea
endpoint: https://git.forteapps.net
autodiscover: true
gitAuthor: "Renovate Bot <renovate@forteapps.net>"
packageRules:
- matchRepositories: ["**/10x"]
assignees: ["edvard.unsvag"]
reviewers: ["edvard.unsvag"]
- matchRepositories: ["**/auth-sidecar"]
assignees: ["danijel.simeunovic"]
reviewers: ["danijel.simeunovic"]
- matchRepositories: ["**/forte-helm"]
assignees: ["danijel.simeunovic"]
reviewers: ["danijel.simeunovic"]
resources:
requests: { cpu: 500m, memory: 1Gi }
limits: { cpu: "2", memory: 4Gi }
```
**Note**: Assignees and reviewers are only applied at PR creation time. Existing PRs must be closed and recreated for new assignment rules to take effect.
**Secrets**: `renovate-env` (SealedSecret in `secrets` namespace, cloned by Kyverno) containing:
- `RENOVATE_TOKEN` — Gitea PAT with repo write + issue write permissions
- `RENOVATE_GITHUB_COM_TOKEN` — GitHub PAT (public_repo read-only) for changelog fetching
**Setup Steps**:
1. Fill in `private/renovate-env.yaml` with tokens
2. Seal: `kubeseal --format yaml < private/renovate-env.yaml > secrets/renovate-env-sealed.yaml`
3. Commit and push — ArgoCD deploys the CronJob, Kyverno clones the secret
**Verification**:
- `kubectl get cronjob -n renovate` — CronJob exists
- `kubectl create job --from=cronjob/renovate renovate-test -n renovate` — manual trigger
- `kubectl logs -n renovate job/renovate-test` — check logs
--- ---
## Kyverno Policies ## Kyverno Policies
@@ -764,6 +1074,19 @@ spec:
**Label Requirement**: Secrets must have `allowedToBeCloned: "true"` **Label Requirement**: Secrets must have `allowedToBeCloned: "true"`
### Keycloak Client Config Cloner
**File**: `cluster-resources/policies/keycloak-client-cloner.yaml`
**Purpose**: Clones Secrets labeled `keycloak.forteapps.net/client-config: "true"` from app namespaces to the `keycloak` namespace. This allows apps to declare their OIDC client configuration in their own namespace, which the [Keycloak Client Registrar](#keycloak-client-registrar) then processes.
**Trigger**: Any Secret with label `keycloak.forteapps.net/client-config: "true"` created outside the `keycloak` namespace.
**Behavior**:
- Generates a copy of the Secret in the `keycloak` namespace with the same name
- Adds source tracking annotations (`keycloak.forteapps.net/source-namespace`, `keycloak.forteapps.net/source-name`)
- `synchronize: true` — changes to the source Secret are reflected in the clone
### Default Namespace Blocker ### Default Namespace Blocker
**File**: `cluster-resources/policies/default-ns-blocker.yaml` **File**: `cluster-resources/policies/default-ns-blocker.yaml`
@@ -853,7 +1176,7 @@ policies.forteapps.io/auth-token-secret-name: "auth-tokens"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
# Optional customization # Optional customization
policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory" policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar"
policies.forteapps.io/auth-image-version: "latest" policies.forteapps.io/auth-image-version: "latest"
``` ```
@@ -869,7 +1192,7 @@ policies.forteapps.io/auth-oidc-client-id: "myapp"
policies.forteapps.io/auth-oidc-callback-path: "/auth/callback" policies.forteapps.io/auth-oidc-callback-path: "/auth/callback"
policies.forteapps.io/auth-oidc-scopes: "openid,profile,email" policies.forteapps.io/auth-oidc-scopes: "openid,profile,email"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory" policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar"
policies.forteapps.io/auth-image-version: "latest" policies.forteapps.io/auth-image-version: "latest"
``` ```
@@ -885,7 +1208,7 @@ policies.forteapps.io/auth-mcp-authority: "https://auth.example.com"
policies.forteapps.io/auth-mcp-scopes: "read,write" policies.forteapps.io/auth-mcp-scopes: "read,write"
policies.forteapps.io/auth-upstream-url: "http://localhost:3000" policies.forteapps.io/auth-upstream-url: "http://localhost:3000"
policies.forteapps.io/auth-log-level: "info" policies.forteapps.io/auth-log-level: "info"
policies.forteapps.io/auth-image: "ghcr.io/snothub/stunning-memory" policies.forteapps.io/auth-image: "ghcr.io/fortedigital/auth-sidecar"
policies.forteapps.io/auth-image-version: "latest" policies.forteapps.io/auth-image-version: "latest"
``` ```
@@ -894,7 +1217,7 @@ policies.forteapps.io/auth-image-version: "latest"
**Token Mode**: **Token Mode**:
```yaml ```yaml
name: authn name: authn
image: ghcr.io/snothub/stunning-memory:latest image: ghcr.io/fortedigital/auth-sidecar:latest
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: auth name: auth
@@ -929,7 +1252,7 @@ securityContext:
**OIDC Mode**: **OIDC Mode**:
```yaml ```yaml
name: authn name: authn
image: ghcr.io/snothub/stunning-memory:latest image: ghcr.io/fortedigital/auth-sidecar:latest
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: auth name: auth
@@ -976,7 +1299,7 @@ securityContext:
**MCP Mode**: **MCP Mode**:
```yaml ```yaml
name: authn name: authn
image: ghcr.io/snothub/stunning-memory:latest image: ghcr.io/fortedigital/auth-sidecar:latest
ports: ports:
- containerPort: 8080 - containerPort: 8080
name: auth name: auth
@@ -1185,6 +1508,19 @@ GET /api/v1/query_range?query={promql}&start={time}&end={time}&step={duration}
GET /api/v1/label/__name__/values GET /api/v1/label/__name__/values
``` ```
### Tempo API
```
# Search traces
GET /api/search?q={traceql}
# Get trace by ID
GET /api/traces/{traceID}
# Service tag values
GET /api/v2/search/tag/resource.service.name/values
```
### Loki API ### Loki API
``` ```
@@ -1316,7 +1652,11 @@ team: platform
| **Prometheus** | 2.47.0+ | Latest | | **Prometheus** | 2.47.0+ | Latest |
| **Grafana** | 10.0.0+ | Latest | | **Grafana** | 10.0.0+ | Latest |
| **Loki** | 2.9.0+ | Latest | | **Loki** | 2.9.0+ | Latest |
| **Tempo** | 2.6.0+ | 1.24.4 |
| **Fluent-Bit** | 2.1.0+ | Latest | | **Fluent-Bit** | 2.1.0+ | Latest |
| **Gitea** | 1.25.4 | 12.5.0 |
| **Gitea Act Runner** | Latest | Latest |
| **Renovate** | v43.113.0 | 46.109.0 |
| **PostgreSQL** | 16-alpine | N/A | | **PostgreSQL** | 16-alpine | N/A |
| **Trivy** | Latest | Latest | | **Trivy** | Latest | Latest |
@@ -1328,6 +1668,6 @@ team: platform
--- ---
**Last Updated**: 2026-03-16 **Last Updated**: 2026-04-16
**Maintained By**: Platform Team **Maintained By**: Platform Team
**Version**: 1.0.0 **Version**: 1.0.0

View File

@@ -48,10 +48,10 @@ spec:
resources: resources:
requests: requests:
cpu: 50m cpu: 50m
memory: 64Mi memory: 128Mi
limits: limits:
cpu: 100m cpu: 100m
memory: 128Mi memory: 256Mi
# Service account # Service account
serviceAccount: serviceAccount:

View File

@@ -15,9 +15,11 @@ spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: cluster-resources path: cluster-resources
directory:
exclude: 'network'
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc

View File

@@ -16,9 +16,9 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: apps/overlays/eu path: apps/overlays/upc-dev
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: apps namespace: apps

View File

@@ -23,7 +23,7 @@ spec:
valueFiles: valueFiles:
- $values/infra/values/base/fluent-bit-values.yaml - $values/infra/values/base/fluent-bit-values.yaml
- repoURL: git@github.com:fortedigital/sturdy-adventure.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values

View File

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

52
infra/base/gitea.yaml Normal file
View File

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

View File

@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: grafana-dashboards
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: grafana-dashboards
app.kubernetes.io/part-of: monitoring
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/dashboards
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true

View File

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

View File

@@ -22,9 +22,9 @@ spec:
releaseName: keycloak releaseName: keycloak
valueFiles: valueFiles:
- $values/infra/values/base/keycloak-values.yaml - $values/infra/values/base/keycloak-values.yaml
- $values/infra/values/eu/keycloak-values.yaml - $values/infra/values/upc-dev/keycloak-values.yaml
- repoURL: git@github.com:fortedigital/sturdy-adventure.git - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
ref: values ref: values
@@ -41,3 +41,9 @@ spec:
- CreateNamespace=true - CreateNamespace=true
- Validate=true - Validate=true
- ServerSideApply=true - ServerSideApply=true
ignoreDifferences:
- group: batch
kind: CronJob
jsonPointers:
- /spec/jobTemplate/spec/template/spec/containers/0/args

View File

@@ -15,3 +15,9 @@ resources:
- cluster-resources-application.yaml - cluster-resources-application.yaml
- kyverno-policies.yaml - kyverno-policies.yaml
- secrets.yaml - secrets.yaml
- gitea.yaml
- gitea-actions.yaml
- renovate.yaml
- tempo.yaml
- grafana-dashboards.yaml
- network-policies-application.yaml

View File

@@ -15,7 +15,7 @@ spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD targetRevision: HEAD
path: cluster-resources/policies path: cluster-resources/policies

View File

@@ -39,6 +39,10 @@ spec:
targetRevision: v3.7.0 # Update to latest stable version targetRevision: v3.7.0 # Update to latest stable version
helm: helm:
releaseName: kyverno releaseName: kyverno
valuesObject:
grafana:
enabled: true
namespace: monitoring
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc

View File

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

View File

@@ -0,0 +1,33 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: network-policies
namespace: argocd
labels:
app.kubernetes.io/name: network-policies
app.kubernetes.io/part-of: platform
app.kubernetes.io/managed-by: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: cluster-resources/network
destination:
server: https://kubernetes.default.svc
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- Validate=true
- ServerSideApply=true

View File

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

42
infra/base/renovate.yaml Normal file
View File

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

View File

@@ -17,8 +17,8 @@ metadata:
spec: spec:
project: default project: default
source: source:
repoURL: git@github.com:fortedigital/sturdy-adventure.git repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
path: secrets/eu path: secrets/upc-dev
destination: destination:
server: https://kubernetes.default.svc server: https://kubernetes.default.svc
namespace: secrets namespace: secrets

42
infra/base/tempo.yaml Normal file
View File

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

View File

@@ -29,7 +29,7 @@ spec:
releaseName: traefik releaseName: traefik
valueFiles: valueFiles:
- $values/infra/values/base/traefik-values.yaml - $values/infra/values/base/traefik-values.yaml
- $values/infra/values/eu/traefik-values.yaml - $values/infra/values/upc-dev/traefik-values.yaml
- repoURL: git@github.com:fortedigital/sturdy-adventure.git - repoURL: git@github.com:fortedigital/sturdy-adventure.git
targetRevision: HEAD targetRevision: HEAD

View File

@@ -0,0 +1,148 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"panels": [
{
"title": "Log Volume",
"type": "timeseries",
"gridPos": {
"h": 6,
"w": 24,
"x": 0,
"y": 0
},
"datasource": {
"type": "loki",
"uid": "loki"
},
"targets": [
{
"expr": "sum(count_over_time({namespace=\"dot-ai\"} [1m])) by (pod)",
"legendFormat": "{{pod}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "bars",
"fillOpacity": 50,
"stacking": {
"mode": "normal"
}
}
},
"overrides": []
}
},
{
"title": "Logs by Pod",
"type": "logs",
"gridPos": {
"h": 16,
"w": 24,
"x": 0,
"y": 6
},
"datasource": {
"type": "loki",
"uid": "loki"
},
"targets": [
{
"expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | line_format `{{.pod}} |{{or .message .msg .log}}`",
"refId": "A"
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": false,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"displayedFields": [
"pod",
"level"
]
}
},
{
"title": "Errors & Warnings",
"type": "logs",
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 22
},
"datasource": {
"type": "loki",
"uid": "loki"
},
"targets": [
{
"expr": "{namespace=\"dot-ai\", pod=~\"$pod\"} | json log=\"log\", message=\"message\", msg=\"msg\", level=\"level\", stream=\"stream\" | label_format level=`{{if .level}}{{.level}}{{else if eq .stream \"stderr\"}}error{{else}}info{{end}}` | level=~`error|warn|warning|fatal|panic` | line_format `{{.pod}} |{{or .message .msg .log}}`",
"refId": "A"
}
],
"options": {
"showTime": true,
"showLabels": false,
"showCommonLabels": false,
"wrapLogMessage": true,
"prettifyLogMessage": false,
"enableLogDetails": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"displayedFields": [
"pod",
"level"
]
}
}
],
"schemaVersion": 39,
"tags": [
"dot-ai",
"logs",
"loki"
],
"templating": {
"list": [
{
"name": "pod",
"type": "query",
"datasource": {
"type": "loki",
"uid": "loki"
},
"query": {
"label": "pod",
"stream": "{namespace=\"dot-ai\"}",
"type": 1
},
"includeAll": true,
"multi": true,
"current": {
"selected": true,
"text": "All",
"value": "$__all"
}
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"title": "dot-ai Logs",
"uid": "dot-ai-logs"
}

View File

@@ -0,0 +1,25 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: monitoring
generatorOptions:
disableNameSuffixHash: true
labels:
grafana_dashboard: "1"
configMapGenerator:
- name: grafana-dashboard-trivy
files:
- trivy.json
- name: grafana-dashboard-traefik-loki
files:
- traefik-loki.json
- name: grafana-dashboard-dot-ai-logs
files:
- dot-ai-logs.json
- name: grafana-dashboard-opencost
files:
- opencost.json
- name: grafana-dashboard-pod-security
files:
- pod-security.json

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,399 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"links": [],
"panels": [
{
"title": "Enforced Denials",
"description": "Pods rejected by Pod Security Standards (enforce mode)",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 0, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "red" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Audit Violations",
"description": "Pods that violate audit-level policy (allowed but logged)",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 6, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"audit\"}[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "orange" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Warnings",
"description": "Pods that triggered warn-level policy (allowed with warning)",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 12, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\", mode=\"warn\"}[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "green" },
{ "value": 1, "color": "yellow" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Total Evaluations",
"description": "All pod security evaluations across all modes",
"type": "stat",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 5, "w": 6, "x": 18, "y": 0 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total[$__range])) or vector(0)",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"noValue": "0",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "blue" }
]
}
},
"overrides": []
},
"options": {
"reduceOptions": { "calcs": ["lastNotNull"] },
"colorMode": "background",
"textMode": "auto"
}
},
{
"title": "Violation Rate by Mode",
"description": "Rate of policy violations over time, grouped by enforcement mode",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[5m]))",
"legendFormat": "enforce (denied)",
"refId": "A"
},
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"audit\"}[5m]))",
"legendFormat": "audit",
"refId": "B"
},
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"warn\"}[5m]))",
"legendFormat": "warn",
"refId": "C"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 15,
"pointSize": 5,
"showPoints": "auto"
},
"unit": "ops"
},
"overrides": [
{
"matcher": { "id": "byName", "options": "enforce (denied)" },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "audit" },
"properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "warn" },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
}
]
}
},
{
"title": "Violations by Policy Level",
"description": "Violation rate grouped by the PSS level that was violated",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\"}[5m])) by (policy_level)",
"legendFormat": "{{ policy_level }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 15,
"pointSize": 5,
"showPoints": "auto"
},
"unit": "ops"
},
"overrides": [
{
"matcher": { "id": "byName", "options": "restricted" },
"properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "baseline" },
"properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }]
},
{
"matcher": { "id": "byName", "options": "privileged" },
"properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }]
}
]
}
},
{
"title": "Enforced Denials by Namespace",
"description": "Pods blocked per namespace (enforce mode only)",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 13 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=\"enforce\"}[5m])) by (resource_namespace)",
"legendFormat": "{{ resource_namespace }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "bars",
"lineWidth": 1,
"fillOpacity": 80,
"stacking": { "mode": "normal" }
},
"unit": "ops"
},
"overrides": []
}
},
{
"title": "Audit + Warn Violations by Namespace",
"description": "Non-enforced violations per namespace — candidates for tightening",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 13 },
"targets": [
{
"expr": "sum(rate(pod_security_evaluations_total{decision=\"deny\", mode=~\"audit|warn\"}[5m])) by (resource_namespace)",
"legendFormat": "{{ resource_namespace }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "bars",
"lineWidth": 1,
"fillOpacity": 80,
"stacking": { "mode": "normal" }
},
"unit": "ops"
},
"overrides": []
}
},
{
"title": "Violations Breakdown",
"description": "Detailed breakdown of all policy violations",
"type": "table",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 10, "w": 24, "x": 0, "y": 21 },
"targets": [
{
"expr": "sum(increase(pod_security_evaluations_total{decision=\"deny\"}[$__range])) by (resource_namespace, policy_level, mode, request_operation) > 0",
"format": "table",
"instant": true,
"refId": "A"
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": { "Time": true },
"renameByName": {
"resource_namespace": "Namespace",
"policy_level": "Policy Level",
"mode": "Mode",
"request_operation": "Operation",
"Value": "Violations"
},
"indexByName": {
"resource_namespace": 0,
"policy_level": 1,
"mode": 2,
"request_operation": 3,
"Value": 4
}
}
},
{
"id": "sortBy",
"options": {
"fields": {},
"sort": [
{ "field": "Violations", "desc": true }
]
}
}
],
"fieldConfig": {
"defaults": {},
"overrides": [
{
"matcher": { "id": "byName", "options": "Mode" },
"properties": [
{
"id": "mappings",
"value": [
{ "type": "value", "options": { "enforce": { "text": "Enforce", "color": "red" }, "audit": { "text": "Audit", "color": "orange" }, "warn": { "text": "Warn", "color": "yellow" } } }
]
}
]
},
{
"matcher": { "id": "byName", "options": "Violations" },
"properties": [
{
"id": "custom.cellOptions",
"value": { "type": "color-background", "mode": "gradient" }
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{ "value": null, "color": "transparent" },
{ "value": 1, "color": "orange" },
{ "value": 100, "color": "red" }
]
}
}
]
}
]
}
},
{
"title": "Exemptions",
"description": "Pods exempted from policy evaluation",
"type": "timeseries",
"datasource": { "type": "prometheus" },
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 31 },
"targets": [
{
"expr": "sum(rate(pod_security_exemptions_total[5m])) by (request_namespace)",
"legendFormat": "{{ request_namespace }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"drawStyle": "line",
"lineWidth": 2,
"fillOpacity": 10
},
"unit": "ops"
},
"overrides": []
}
}
],
"schemaVersion": 39,
"tags": [
"security",
"pod-security",
"pss",
"compliance"
],
"templating": {
"list": []
},
"time": {
"from": "now-24h",
"to": "now"
},
"title": "Pod Security Violations",
"uid": "pod-security-violations"
}

File diff suppressed because it is too large Load Diff

1841
infra/dashboards/trivy.json Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -4,47 +4,47 @@ resources:
- ../../base - ../../base
patches: patches:
# Traefik: swap eu → us in valueFiles # Traefik: swap upc-dev → upc-prod in valueFiles
- target: - target:
kind: Application kind: Application
name: traefik name: traefik
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/us/traefik-values.yaml value: $values/infra/values/upc-prod/traefik-values.yaml
# Keycloak: swap eu → us # Keycloak: swap upc-dev → upc-prod
- target: - target:
kind: Application kind: Application
name: keycloak name: keycloak
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/us/keycloak-values.yaml value: $values/infra/values/upc-prod/keycloak-values.yaml
# Grafana: swap eu → us # Grafana: swap upc-dev → upc-prod
- target: - target:
kind: Application kind: Application
name: grafana name: grafana
patch: | patch: |
- op: replace - op: replace
path: /spec/sources/0/helm/valueFiles/1 path: /spec/sources/0/helm/valueFiles/1
value: $values/infra/values/us/grafana-values.yaml value: $values/infra/values/upc-prod/grafana-values.yaml
# Secrets: change path to us # Secrets: change path to upc-prod
- target: - target:
kind: Application kind: Application
name: secrets name: secrets
patch: | patch: |
- op: replace - op: replace
path: /spec/source/path path: /spec/source/path
value: secrets/us value: secrets/upc-prod
# Enterprise-apps: point to us overlay # Enterprise-apps: point to upc-prod overlay
- target: - target:
kind: Application kind: Application
name: enterprise-apps name: enterprise-apps
patch: | patch: |
- op: replace - op: replace
path: /spec/source/path path: /spec/source/path
value: apps/overlays/us value: apps/overlays/upc-prod

View File

@@ -6,10 +6,6 @@ configs:
application.resourceTrackingMethod: annotation application.resourceTrackingMethod: annotation
timeout.reconciliation: 60s timeout.reconciliation: 60s
admin.enabled: "true" admin.enabled: "true"
repositories: |
- type: git
url: https://github.com/snothub
name: github-repo
params: params:
"server.insecure": true "server.insecure": true
server: server:
@@ -32,7 +28,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}" "payload": "🖥️ {{ .context.clusterName }}: 🔄 *{{ .app.metadata.name }}* is syncing...\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}"
} }
template.app-sync-succeeded: | template.app-sync-succeeded: |
webhook: webhook:
@@ -40,7 +36,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷 Image: {{ . }}{{ end }}" "payload": "🖥️ {{ .context.clusterName }}: ✅ *{{ .app.metadata.name }}* sync succeeded\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷 Image: {{ . }}{{ end }}"
} }
template.app-sync-failed: | template.app-sync-failed: |
webhook: webhook:
@@ -48,7 +44,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | substr 0 7 }}\n⚠ Message: {{ .app.status.operationState.message }}" "payload": "🖥️ {{ .context.clusterName }}: ❌ *{{ .app.metadata.name }}* sync failed\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}\n⚠ Message: {{ .app.status.operationState.message }}"
} }
template.app-degraded: | template.app-degraded: |
webhook: webhook:
@@ -56,7 +52,7 @@ notifications:
method: POST method: POST
body: | body: |
{ {
"payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n💬 Message: {{ .app.status.health.message }}" "payload": "🖥️ {{ .context.clusterName }}: ⚠️ *{{ .app.metadata.name }}* is degraded\n🏥 Health: {{ .app.status.health.status }}\n📦 Revision: {{ .app.status.sync.revision | default `n/a` | substr 0 7 }}{{ range .app.status.summary.images }}\n🏷 Image: {{ . }}{{ end }}"
} }
# Define notification triggers # Define notification triggers
@@ -65,7 +61,7 @@ notifications:
- when: app.status.operationState.phase in ['Running'] - when: app.status.operationState.phase in ['Running']
send: [app-syncing] send: [app-syncing]
trigger.on-sync-succeeded: | trigger.on-sync-succeeded: |
- when: app.status.operationState.phase in ['Succeeded'] - when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'
send: [app-sync-succeeded] send: [app-sync-succeeded]
trigger.on-sync-failed: | trigger.on-sync-failed: |
- when: app.status.operationState.phase in ['Failed'] - when: app.status.operationState.phase in ['Failed']

View File

@@ -48,7 +48,8 @@ config:
Match kube.* Match kube.*
Host loki-gateway.monitoring.svc.cluster.local Host loki-gateway.monitoring.svc.cluster.local
Port 80 Port 80
Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name'] Labels job=fluent-bit, namespace=$kubernetes['namespace_name'], pod=$kubernetes['pod_name'], container=$kubernetes['container_name'], stream=$stream
Auto_Kubernetes_Labels Off
Line_Format json Line_Format json
[OUTPUT] [OUTPUT]

View File

@@ -0,0 +1,36 @@
## Gitea Act Runner - Helm values
## Chart: actions v0.0.5 (https://dl.gitea.com/charts)
enabled: true
giteaRootURL: https://git.forteapps.net
existingSecret: gitea-runner-token
existingSecretKey: token
statefulset:
replicas: 3
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
actRunner:
config: |
log:
level: info
cache:
enabled: false
container:
require_docker: true
docker_timeout: 300s
runner:
labels:
- "ubuntu-latest:docker://catthehacker/ubuntu:act-22.04"
- "ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04"
dind:
rootless: false

View File

@@ -0,0 +1,181 @@
# Gitea Helm Chart Values
# Host: git.forteapps.net
# Chart: gitea v12.5.0 (app v1.25.4)
# Repo: https://dl.gitea.com/charts
# -- Admin account (password from sealed secret)
gitea:
admin:
existingSecret: gitea-credentials
email: admin@forteapps.net
# -- Gitea app.ini configuration
config:
APP_NAME: "Forte Git"
server:
DOMAIN: git.forteapps.net
ROOT_URL: https://git.forteapps.net
SSH_DOMAIN: git.forteapps.net
SSH_PORT: 2222
LFS_START_SERVER: true
ENABLE_GITEA_PAGES: true
ENABLE_BASIC_AUTH_CHALLENGE: true
service:
DISABLE_REGISTRATION: false
DEFAULT_ALLOW_CREATE_ORGANIZATION: false
REQUIRE_SIGNIN_VIEW: false
ALLOW_ONLY_EXTERNAL_REGISTRATION: true
ENABLE_BASIC_AUTHENTICATION: true
ENABLE_PASSWORD_SIGNIN_FORM: false
ENABLE_NOTIFY_MAIL: true
openid:
ENABLE_OPENID_SIGNIN: false
ENABLE_OPENID_SIGNUP: false
oauth2:
ENABLED: true
ENABLE_AUTO_REGISTRATION: true
USERNAME: email
session:
PROVIDER: db
cache:
ADAPTER: memory
database:
DB_TYPE: postgres
metrics:
ENABLED: true
repository:
DEFAULT_BRANCH: main
DEFAULT_PRIVATE: last
actions:
ENABLED: true
packages:
ENABLED: true
indexer:
ISSUE_INDEXER_TYPE: bleve
REPO_INDEXER_ENABLED: true
mailer:
ENABLED: true
PROTOCOL: smtp+starttls
SMTP_ADDR: smtp.office365.com
SMTP_PORT: 587
FROM: "noreply@fortedigital.com"
admin:
DEFAULT_EMAIL_NOTIFICATIONS: enabled
# -- SMTP credentials injected from secret (USER and PASSWD)
additionalConfigFromEnvs:
- name: GITEA__mailer__USER
valueFrom:
secretKeyRef:
name: gitea-smtp-secret
key: username
- name: GITEA__mailer__PASSWD
valueFrom:
secretKeyRef:
name: gitea-smtp-secret
key: password
# -- OIDC authentication via Forte
oauth:
- name: "Forte"
provider: "openidConnect"
existingSecret: gitea-oidc-credentials
key: gitea
autoDiscoverUrl: "https://id.forteapps.net/realms/forte/.well-known/openid-configuration"
scopes: "openid email profile organization"
groupClaimName: "groups"
adminGroup: ""
restrictedGroup: ""
# -- Prometheus metrics (scraped via annotations, no ServiceMonitor CRD needed)
metrics:
enabled: true
serviceMonitor:
enabled: false
# -- Ingress via Traefik with Let's Encrypt TLS
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: git.forteapps.net
paths:
- path: /
pathType: Prefix
tls:
- secretName: gitea-tls
hosts:
- git.forteapps.net
# -- Git repository storage
persistence:
enabled: true
size: 10Gi
accessModes:
- ReadWriteOnce
storageClass: upcloud-block-storage-maxiops
# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes
strategy:
type: Recreate
# -- Pod resources
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# -- Embedded PostgreSQL (Bitnami subchart)
# Password auto-generated by the subchart; Gitea chart auto-wires the connection.
postgresql:
enabled: true
auth:
username: gitea
database: gitea
primary:
persistence:
enabled: true
size: 8Gi
storageClass: upcloud-block-storage-maxiops
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
# -- Disable PostgreSQL HA (using single-instance postgresql above)
postgresql-ha:
enabled: false
# -- Disable Redis cluster (use in-memory cache instead)
redis-cluster:
enabled: false
# -- Disable test pod
test:
enabled: false
# -- SSH service (ClusterIP, exposed externally via Traefik TCP IngressRoute on port 2222)
service:
ssh:
type: ClusterIP
port: 22

File diff suppressed because it is too large Load Diff

View File

@@ -19,12 +19,18 @@ ingress:
annotations: annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod cert-manager.io/cluster-issuer: letsencrypt-prod
metrics:
enabled: true
prometheusRule:
namespace: monitoring
enabled: true
resources: resources:
requests: requests:
cpu: 250m cpu: 250m
memory: 512Mi memory: 512Mi
limits: limits:
cpu: "1" cpu: 500m
memory: 1Gi memory: 1Gi
postgresql: postgresql:
@@ -56,5 +62,395 @@ keycloakConfigCli:
"registrationAllowed": false, "registrationAllowed": false,
"loginWithEmailAllowed": true, "loginWithEmailAllowed": true,
"resetPasswordAllowed": true, "resetPasswordAllowed": true,
"rememberMe": true "rememberMe": true,
"clients": [
{
"clientId": "gitea",
"name": "Gitea",
"enabled": true,
"protocol": "openid-connect",
"clientAuthenticatorType": "client-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"publicClient": false,
"redirectUris": ["https://git.forteapps.net/*"],
"webOrigins": ["https://git.forteapps.net"],
"defaultClientScopes": ["openid", "email", "profile"],
"attributes": {
"k8s.secret.sync": "true",
"k8s.secret.namespace": "gitea",
"k8s.secret.name": "gitea-oidc-credentials",
"k8s.secret.client-id-key": "key",
"k8s.secret.client-secret-key": "secret"
},
"protocolMappers": [
{
"name": "email_verified",
"protocol": "openid-connect",
"protocolMapper": "oidc-hardcoded-claim-mapper",
"config": {
"claim.name": "email_verified",
"claim.value": "true",
"jsonType.label": "boolean",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
}
]
} }
extraDeploy:
# -- ServiceAccount for the client registrar CronJob
- apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak-client-registrar
namespace: keycloak
# -- ClusterRole granting access to secrets and namespaces
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: keycloak-client-registrar
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "create", "update", "patch"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
# -- ClusterRoleBinding for the registrar ServiceAccount
- apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: keycloak-client-registrar
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: keycloak-client-registrar
subjects:
- kind: ServiceAccount
name: keycloak-client-registrar
namespace: keycloak
# -- CronJob: registers Keycloak clients and syncs secrets
- apiVersion: batch/v1
kind: CronJob
metadata:
name: keycloak-client-registrar
namespace: keycloak
spec:
schedule: "*/2 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 3
template:
spec:
serviceAccountName: keycloak-client-registrar
restartPolicy: Never
containers:
- name: registrar
image: alpine:3.20
command: ["/bin/sh", "-c"]
args:
- |
set -e
apk add --no-cache curl jq > /dev/null 2>&1
KEYCLOAK_URL="http://keycloak:80"
REALM="forte"
K8S_API="https://kubernetes.default.svc"
SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
CENTRAL_NS="secrets"
# --- Authenticate to Keycloak Admin API ---
ADMIN_USER="admin"
ADMIN_PASS=$(cat /secrets/admin-password)
echo "Authenticating to Keycloak..."
TOKEN=$(curl -sf -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
-d "client_id=admin-cli" \
-d "username=${ADMIN_USER}" \
-d "password=${ADMIN_PASS}" \
-d "grant_type=password" | jq -r '.access_token')
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "ERROR: Failed to authenticate to Keycloak"
exit 1
fi
# --- Helper functions ---
# Upsert a K8s Secret: try PUT (update), fall back to POST (create)
upsert_secret() {
local ns="$1" name="$2" manifest="$3"
local code
code=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
-H "Content-Type: application/json" \
-X PUT -d "$manifest" \
"${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}")
if [ "$code" = "200" ]; then
echo " Updated secret '${ns}/${name}'"
elif [ "$code" = "404" ]; then
code=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
-H "Content-Type: application/json" \
-X POST -d "$manifest" \
"${K8S_API}/api/v1/namespaces/${ns}/secrets")
if [ "$code" = "201" ]; then
echo " Created secret '${ns}/${name}'"
else
echo " ERROR: Failed to create secret '${ns}/${name}' (HTTP ${code})"
return 1
fi
else
echo " ERROR: Failed to update secret '${ns}/${name}' (HTTP ${code})"
return 1
fi
}
# Build a credential Secret JSON manifest
build_credential_secret() {
local ns="$1" name="$2" id_key="$3" secret_key="$4" b64_id="$5" b64_secret="$6"
cat <<MANIFEST
{
"apiVersion": "v1",
"kind": "Secret",
"metadata": {
"name": "${name}",
"namespace": "${ns}",
"labels": {
"app.kubernetes.io/managed-by": "keycloak-client-registrar"
}
},
"type": "Opaque",
"data": {
"${id_key}": "${b64_id}",
"${secret_key}": "${b64_secret}"
}
}
MANIFEST
}
# Sync credentials to target + central namespace
sync_credentials() {
local client_id="$1" client_uuid="$2" target_ns="$3" target_name="$4" id_key="$5" secret_key="$6"
# Get the client secret from Keycloak
local secret_value
secret_value=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${client_uuid}/client-secret" \
| jq -r '.value')
if [ -z "$secret_value" ] || [ "$secret_value" = "null" ]; then
echo " WARNING: No secret found for client '${client_id}', skipping"
return 0
fi
local b64_id b64_secret
b64_id=$(printf '%s' "$client_id" | base64 | tr -d '\n')
b64_secret=$(printf '%s' "$secret_value" | base64 | tr -d '\n')
# Write to target namespace (if it exists)
local ns_status
ns_status=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
"${K8S_API}/api/v1/namespaces/${target_ns}")
if [ "$ns_status" = "200" ]; then
local manifest
manifest=$(build_credential_secret "$target_ns" "$target_name" "$id_key" "$secret_key" "$b64_id" "$b64_secret")
upsert_secret "$target_ns" "$target_name" "$manifest" || return 1
else
echo " WARNING: Namespace '${target_ns}' does not exist, skipping target"
fi
# Always write a central copy to the secrets namespace
local central_manifest
central_manifest=$(build_credential_secret "$CENTRAL_NS" "$target_name" "$id_key" "$secret_key" "$b64_id" "$b64_secret")
upsert_secret "$CENTRAL_NS" "$target_name" "$central_manifest" || return 1
}
# Annotate a K8s Secret with sync status
annotate_secret() {
local ns="$1" name="$2" key="$3" value="$4"
local patch
patch=$(printf '{"metadata":{"annotations":{"%s":"%s"}}}' "$key" "$value")
curl -sf -o /dev/null \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
-X PATCH -d "$patch" \
"${K8S_API}/api/v1/namespaces/${ns}/secrets/${name}"
}
# =============================================
# LEGACY PATH — sync existing realm clients
# =============================================
echo "=== Legacy sync: clients with k8s.secret.sync=true ==="
CLIENTS=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients")
SYNC_CLIENTS=$(echo "$CLIENTS" | jq -c '[.[] | select(.attributes["k8s.secret.sync"] == "true")]')
COUNT=$(echo "$SYNC_CLIENTS" | jq 'length')
echo "Found ${COUNT} legacy client(s) with sync enabled"
echo "$SYNC_CLIENTS" | jq -c '.[]' | while read -r CLIENT; do
CLIENT_ID=$(echo "$CLIENT" | jq -r '.clientId')
CLIENT_UUID=$(echo "$CLIENT" | jq -r '.id')
TARGET_NS=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.namespace"]')
TARGET_NAME=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.name"]')
ID_KEY=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.client-id-key"] // "client-id"')
SECRET_KEY=$(echo "$CLIENT" | jq -r '.attributes["k8s.secret.client-secret-key"] // "client-secret"')
echo "Processing legacy client '${CLIENT_ID}' -> '${TARGET_NS}/${TARGET_NAME}' (keys: ${ID_KEY}, ${SECRET_KEY})"
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$TARGET_NS" "$TARGET_NAME" "$ID_KEY" "$SECRET_KEY"
done
# =============================================
# NEW PATH — self-service config Secrets
# =============================================
echo ""
echo "=== Self-service: config Secrets with label keycloak.forteapps.net/client-config=true ==="
CONFIG_SECRETS=$(curl -sf \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
"${K8S_API}/api/v1/namespaces/keycloak/secrets?labelSelector=keycloak.forteapps.net/client-config=true")
CONFIG_COUNT=$(echo "$CONFIG_SECRETS" | jq '.items | length')
echo "Found ${CONFIG_COUNT} config Secret(s) to process"
echo "$CONFIG_SECRETS" | jq -c '.items[]' | while read -r CONFIG_SECRET; do
CONFIG_NAME=$(echo "$CONFIG_SECRET" | jq -r '.metadata.name')
SOURCE_NS=$(echo "$CONFIG_SECRET" | jq -r '.metadata.annotations["keycloak.forteapps.net/source-namespace"] // .metadata.labels["keycloak.forteapps.net/source-namespace"] // "unknown"')
# Decode client.json from the Secret data
CLIENT_JSON_B64=$(echo "$CONFIG_SECRET" | jq -r '.data["client.json"] // empty')
if [ -z "$CLIENT_JSON_B64" ]; then
echo "WARNING: Config Secret '${CONFIG_NAME}' missing client.json field, skipping"
continue
fi
CLIENT_JSON=$(printf '%s' "$CLIENT_JSON_B64" | base64 -d)
CLIENT_ID=$(echo "$CLIENT_JSON" | jq -r '.clientId')
echo "Processing self-service client '${CLIENT_ID}' from config '${CONFIG_NAME}'"
# Compute config hash for change detection
CONFIG_HASH=$(printf '%s' "$CLIENT_JSON" | sha256sum | cut -d' ' -f1)
EXISTING_HASH=$(echo "$CONFIG_SECRET" | jq -r '.metadata.annotations["keycloak.forteapps.net/config-hash"] // ""')
# Extract secret delivery config from client.json
CRED_NS=$(echo "$CLIENT_JSON" | jq -r '.secret.namespace // "'"${SOURCE_NS}"'"')
CRED_NAME=$(echo "$CLIENT_JSON" | jq -r '.secret.name // "'"${CLIENT_ID}"'-oidc-credentials"')
CRED_ID_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientId // "client-id"')
CRED_SECRET_KEY=$(echo "$CLIENT_JSON" | jq -r '.secret.keys.clientSecret // "client-secret"')
# Check if credential Secret already exists in target namespace
CRED_EXISTS=$(curl -sf -o /dev/null -w "%{http_code}" \
--cacert "$CA_CERT" \
-H "Authorization: Bearer ${SA_TOKEN}" \
"${K8S_API}/api/v1/namespaces/${CRED_NS}/secrets/${CRED_NAME}")
# Skip if hash matches and credential Secret exists
if [ "$CONFIG_HASH" = "$EXISTING_HASH" ] && [ "$CRED_EXISTS" = "200" ]; then
echo " No changes detected, skipping"
continue
fi
# Build Keycloak client representation (strip our secret delivery config)
KC_CLIENT=$(echo "$CLIENT_JSON" | jq '{
clientId: .clientId,
name: .name,
enabled: true,
protocol: "openid-connect",
clientAuthenticatorType: "client-secret",
standardFlowEnabled: true,
directAccessGrantsEnabled: false,
publicClient: false,
redirectUris: .redirectUris,
webOrigins: .webOrigins,
defaultClientScopes: .defaultClientScopes,
protocolMappers: (.protocolMappers // [])
}')
# Check if client already exists
EXISTING=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
| jq -r '.[0].id // empty')
if [ -n "$EXISTING" ]; then
echo " Updating existing Keycloak client (uuid: ${EXISTING})"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-X PUT -d "$KC_CLIENT" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients/${EXISTING}")
if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "200" ]; then
echo " ERROR: Failed to update client '${CLIENT_ID}' (HTTP ${HTTP_CODE})"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "error"
continue
fi
CLIENT_UUID="$EXISTING"
else
echo " Creating new Keycloak client '${CLIENT_ID}'"
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-X POST -d "$KC_CLIENT" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients")
if [ "$HTTP_CODE" != "201" ]; then
echo " ERROR: Failed to create client '${CLIENT_ID}' (HTTP ${HTTP_CODE})"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "error"
continue
fi
# Fetch the newly created client's UUID
CLIENT_UUID=$(curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KEYCLOAK_URL}/admin/realms/${REALM}/clients?clientId=${CLIENT_ID}" \
| jq -r '.[0].id')
fi
# Sync credentials to target namespace
sync_credentials "$CLIENT_ID" "$CLIENT_UUID" "$CRED_NS" "$CRED_NAME" "$CRED_ID_KEY" "$CRED_SECRET_KEY"
# Annotate config Secret with hash and sync status
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/config-hash" "$CONFIG_HASH"
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/sync-status" "synced"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
annotate_secret "keycloak" "$CONFIG_NAME" "keycloak.forteapps.net/last-sync" "$TIMESTAMP"
echo " Synced successfully"
done
echo ""
echo "Client registrar run complete"
volumeMounts:
- name: keycloak-credentials
mountPath: /secrets
readOnly: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: keycloak-credentials
secret:
secretName: keycloak-credentials
items:
- key: admin-password
path: admin-password

View File

@@ -20,20 +20,22 @@ loki:
limits_config: limits_config:
reject_old_samples: true reject_old_samples: true
reject_old_samples_max_age: 168h reject_old_samples_max_age: 168h
ingestion_rate_mb: 10 ingestion_rate_mb: 15
ingestion_burst_size_mb: 20 ingestion_burst_size_mb: 30
max_line_size: 512KB max_line_size: 512KB
chunksCache: chunksCache:
enabled: false enabled: false
resultsCache:
enabled: false
singleBinary: singleBinary:
replicas: 1 replicas: 1
resources: resources:
requests: requests:
cpu: 100m cpu: 50m
memory: 512Mi memory: 256Mi
limits: limits:
cpu: 200m cpu: 100m
memory: 2Gi memory: 1Gi
read: read:
replicas: 0 replicas: 0
backend: backend:

View File

@@ -0,0 +1,30 @@
opencost:
exporter:
defaultClusterId: launchpad
extraEnv:
EMIT_KSM_V1_METRICS: "false"
EMIT_KSM_V1_METRICS_ONLY: "true"
prometheus:
internal:
enabled: true
serviceName: prometheus-server
namespaceName: monitoring
port: 80
customPricing:
enabled: true
provider: custom
costModel:
description: "UpCloud 4-node cluster pricing"
CPU: "5.86"
RAM: "1.46"
GPU: "0"
storage: "0.34"
zoneNetworkEgress: "0"
regionNetworkEgress: "0"
internetNetworkEgress: "0"
ui:
enabled: false
service:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9003"

View File

@@ -6,13 +6,15 @@ server:
resources: resources:
requests: requests:
cpu: 250m cpu: 150m
memory: 512Mi memory: 512Mi
limits: limits:
cpu: 500m cpu: 300m
memory: 1Gi memory: 1Gi
enableLifecycle: true enableLifecycle: true
extraFlags:
- web.enable-remote-write-receiver
extraScrapeConfigs: | extraScrapeConfigs: |
- job_name: kyverno - job_name: kyverno
@@ -56,5 +58,24 @@ extraScrapeConfigs: |
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance] - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_instance]
target_label: instance target_label: instance
- job_name: traefik
scrape_interval: 15s
metrics_path: /metrics
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- traefik-system
relabel_configs:
- source_labels: [__meta_kubernetes_endpoint_port_name]
regex: metrics
action: keep
- source_labels: [__meta_kubernetes_service_name]
target_label: service
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
alertmanager: alertmanager:
enabled: false enabled: false

View File

@@ -0,0 +1,45 @@
cronjob:
schedule: "@daily"
concurrencyPolicy: Forbid
renovate:
config: |
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"platform": "gitea",
"endpoint": "https://git.forteapps.net",
"autodiscover": true,
"gitAuthor": "Renovate Bot <renovate@forteapps.net>",
"packageRules": [
{
"matchRepositories": ["**/10x"],
"assignees": ["edvard.unsvag"],
"reviewers": ["edvard.unsvag"]
},
{
"matchRepositories": ["**/auth-sidecar"],
"assignees": ["danijel.simeunovic"],
"reviewers": ["danijel.simeunovic"]
},
{
"matchRepositories": ["**/forte-helm"],
"assignees": ["danijel.simeunovic"],
"reviewers": ["danijel.simeunovic"]
}
]
}
envFrom:
- secretRef:
name: renovate-env
env:
LOG_LEVEL: info
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "2"
memory: 4Gi

View File

@@ -0,0 +1,34 @@
tempo:
metricsGenerator:
enabled: true
remoteWriteUrl: "http://prometheus-server.monitoring.svc.cluster.local/api/v1/write"
overrides:
defaults:
metrics_generator:
processors:
- service-graphs
- span-metrics
storage:
trace:
backend: local
local:
path: /var/tempo/traces
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
persistence:
enabled: true
size: 10Gi
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
cpu: 100m
memory: 512Mi

43
mkdocs.yml Normal file
View File

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

91
scripts/gitea-backup.sh Normal file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -euo pipefail
# Gitea backup helper — interacts with the S3 bucket via a temporary pod
# Uses the gitea-backup-s3 secret in the gitea namespace
#
# Usage:
# ./scripts/gitea-backup.sh list # list all backups
# ./scripts/gitea-backup.sh download <filename> # download a backup to current dir
# ./scripts/gitea-backup.sh download latest # download the most recent backup
NAMESPACE="gitea"
SECRET="gitea-backup-s3"
IMAGE="minio/mc:latest"
POD_NAME="gitea-backup-helper"
ALIAS_CMD='mc alias set upcloud ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
cleanup() {
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
}
mc_run() {
cleanup
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
--image="$IMAGE" \
--overrides="{
\"spec\":{\"containers\":[{
\"name\":\"$POD_NAME\",
\"image\":\"$IMAGE\",
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
\"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"],
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
}]}
}" > /dev/null 2>&1
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
kubectl -n "$NAMESPACE" logs "$POD_NAME"
cleanup
}
case "${1:-help}" in
list)
echo "Listing backups..."
mc_run 'mc ls upcloud/${S3_BUCKET}/'
;;
download)
FILE="${2:?Usage: $0 download <filename|latest>}"
if [ "$FILE" = "latest" ]; then
echo "Finding latest backup..."
FILE=$(mc_run 'mc ls upcloud/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
if [ -z "$FILE" ]; then
echo "No backups found."
exit 1
fi
echo "Latest: $FILE"
fi
echo "Downloading $FILE..."
cleanup
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
--image="$IMAGE" \
--overrides="{
\"spec\":{\"containers\":[{
\"name\":\"$POD_NAME\",
\"image\":\"$IMAGE\",
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
}]}
}" > /dev/null 2>&1
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
echo "Saving to ./$FILE ..."
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat upcloud/\${S3_BUCKET}/$FILE" > "./$FILE"
cleanup
echo "Downloaded: ./$FILE"
;;
*)
echo "Gitea backup helper"
echo ""
echo "Usage:"
echo " $0 list List all backups in S3"
echo " $0 download <filename> Download a specific backup"
echo " $0 download latest Download the most recent backup"
;;
esac

165
scripts/gitea-restore.sh Normal file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env bash
set -euo pipefail
# Gitea restore helper — restores a gitea-dump zip into a running Gitea deployment
#
# Prerequisites:
# - Gitea deployed on the target cluster (Helm chart via ArgoCD)
# - kubectl context pointing to the target cluster
# - A gitea-dump zip file (from gitea-backup.sh download or CronJob)
#
# Usage:
# ./scripts/gitea-restore.sh <gitea-dump-file.zip>
#
# What it does:
# 1. Scales Gitea down to 0
# 2. Restores the PostgreSQL database from gitea-db.sql
# 3. Restores Git repositories to the data PVC
# 4. Scales Gitea back up
NAMESPACE="gitea"
DEPLOYMENT="gitea"
PG_STATEFULSET="gitea-postgresql"
PVC="gitea-shared-storage"
HELPER_POD="gitea-restore-helper"
HELPER_IMAGE="alpine:3.20"
PG_USER="gitea"
PG_DB="gitea"
DUMP_FILE="${1:?Usage: $0 <gitea-dump-file.zip>}"
if [ ! -f "$DUMP_FILE" ]; then
echo "Error: file not found: $DUMP_FILE"
exit 1
fi
echo "=== Gitea Restore ==="
echo "Dump file: $DUMP_FILE"
echo ""
# --- Safety prompt ---
read -r -p "This will OVERWRITE the Gitea database and repositories on the current cluster. Continue? [y/N] " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 0
fi
cleanup() {
kubectl -n "$NAMESPACE" delete pod "$HELPER_POD" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
}
trap cleanup EXIT
# --- Step 1: Extract dump locally ---
RESTORE_DIR=$(mktemp -d)
echo ""
echo "[1/5] Extracting dump..."
unzip -q "$DUMP_FILE" -d "$RESTORE_DIR"
# Detect SQL dump file (gitea-db.sql or gitea-dump-*.sql)
SQL_FILE=$(find "$RESTORE_DIR" -maxdepth 1 -name '*.sql' | head -1)
if [ -z "$SQL_FILE" ]; then
echo "Error: no .sql file found in dump."
rm -rf "$RESTORE_DIR"
exit 1
fi
# Detect repos — either a "repos" folder or gitea-repo.zip
if [ -d "$RESTORE_DIR/repos" ]; then
REPOS_SOURCE="dir"
REPOS_PATH="$RESTORE_DIR/repos"
elif [ -f "$RESTORE_DIR/gitea-repo.zip" ]; then
REPOS_SOURCE="zip"
REPOS_PATH="$RESTORE_DIR/gitea-repo.zip"
else
echo "Error: no repos/ directory or gitea-repo.zip found in dump."
rm -rf "$RESTORE_DIR"
exit 1
fi
echo " Found: $(basename "$SQL_FILE") ($(du -h "$SQL_FILE" | cut -f1))"
echo " Found: repos ($REPOS_SOURCE)"
[ -d "$RESTORE_DIR/data" ] && echo " Found: data/"
[ -f "$RESTORE_DIR/app.ini" ] && echo " Found: app.ini (managed by Helm, skipping)"
# --- Step 2: Scale down Gitea ---
echo ""
echo "[2/5] Scaling down Gitea..."
kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=0
kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=60s 2>/dev/null || true
echo " Waiting for pods to terminate..."
kubectl -n "$NAMESPACE" wait --for=delete pod -l app.kubernetes.io/name=gitea --timeout=120s 2>/dev/null || true
echo " Gitea is down."
# --- Step 3: Restore database ---
echo ""
echo "[3/5] Restoring database..."
# Drop and recreate to ensure clean state
kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \
psql -U "$PG_USER" -d postgres -c "DROP DATABASE IF EXISTS ${PG_DB};" 2>/dev/null
kubectl -n "$NAMESPACE" exec "${PG_STATEFULSET}-0" -- \
psql -U "$PG_USER" -d postgres -c "CREATE DATABASE ${PG_DB} OWNER ${PG_USER};" 2>/dev/null
kubectl -n "$NAMESPACE" exec -i "${PG_STATEFULSET}-0" -- \
psql -U "$PG_USER" -d "$PG_DB" < "$SQL_FILE" > /dev/null
echo " Database restored."
# --- Step 4: Restore repositories ---
echo ""
echo "[4/5] Restoring repositories..."
# Need a helper pod on the same node as the PVC (RWO)
cleanup
kubectl -n "$NAMESPACE" run "$HELPER_POD" --restart=Never \
--image="$HELPER_IMAGE" \
--overrides="{
\"spec\":{
\"containers\":[{
\"name\":\"$HELPER_POD\",
\"image\":\"$HELPER_IMAGE\",
\"command\":[\"sleep\",\"3600\"],
\"volumeMounts\":[{\"name\":\"data\",\"mountPath\":\"/data\"}]
}],
\"volumes\":[{
\"name\":\"data\",
\"persistentVolumeClaim\":{\"claimName\":\"$PVC\"}
}]
}
}" > /dev/null 2>&1
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$HELPER_POD" --timeout=120s > /dev/null 2>&1
# Clear existing repos
kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- sh -c "rm -rf /data/gitea/repositories/*" 2>/dev/null || true
# Upload repos — tar via stdin since kubectl cp needs tar in the container
echo " Uploading repositories..."
if [ "$REPOS_SOURCE" = "dir" ]; then
# repos/ directory — tar and stream into the PVC
tar -cf - -C "$REPOS_PATH" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/repositories/"
else
# gitea-repo.zip — stream and extract
cat "$REPOS_PATH" | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "cat > /tmp/gitea-repo.zip && unzip -q -o /tmp/gitea-repo.zip -d /data/gitea/repositories/ && rm /tmp/gitea-repo.zip"
fi
# Restore data/ directory (avatars, attachments, LFS, etc.) if present
if [ -d "$RESTORE_DIR/data" ]; then
echo " Uploading data (avatars, attachments, LFS)..."
tar -cf - -C "$RESTORE_DIR/data" . | kubectl -n "$NAMESPACE" exec -i "$HELPER_POD" -- sh -c "tar -xf - -C /data/gitea/"
fi
# Fix ownership
kubectl -n "$NAMESPACE" exec "$HELPER_POD" -- chown -R 1000:1000 /data/gitea/
echo " Repositories restored."
# --- Step 5: Scale back up ---
echo ""
echo "[5/5] Scaling Gitea back up..."
kubectl -n "$NAMESPACE" scale deployment "$DEPLOYMENT" --replicas=1
kubectl -n "$NAMESPACE" rollout status deployment "$DEPLOYMENT" --timeout=120s
# Cleanup temp dir
rm -rf "$RESTORE_DIR"
echo ""
echo "=== Restore complete ==="
echo "Gitea should be back online with restored data."
echo "Verify at your Gitea URL that repos and users are present."

View File

@@ -0,0 +1,20 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: forte-helm-repo
namespace: argocd
spec:
encryptedData:
project: AgBBeKBtro27DMJ+9YFjSlvv+XWyotBojRbaDVPDfwH0MoXST9zUKZSXDgbuJ5sy8dQYhfCZgtfjcNpWbCmLoURZMm1aPa0U05bG7Hz2EnO+39qcgpH/jzEzp0AsZ9YgMafgI0EyL63RnVmvvcFT81fNe4P6vlKjz+dyhIuXbELhDjabNE+QwsLmQkty7R6isJSx42+aEGliD69FcFjlvwz75L7y2Rck1eodUVbaTX3qdG/bSFCTWawCqnj27oEn+qYwcvrS/B8MsiG8XkcmAxy3QVHAW4waBQ/6IALqBaviXunye7TOujgfn996sQWEZz9e6tVsbh1I4i/1T44rtqiot3EuFMMuYN1C6JMb53lUI9KM/TMWM3U/B/MgFR7DbXRF8/VRkBqVJFv/PZWKxTNbnYEhdPBPZojZNhHntdFZNw7l9Pr251mPI9UCU+Mc+RKtluKb+c40C9Ty1TGaV0DQ3UtA+1zbotDVGx21P7tAwplz22czu0GSUZu3nDDEPoyQoI1YG4TBvqKwuFbxJZASUglDmwyzFG15QIn03zE661ETR1ETXMbqxJR8yWmY7G5R1aNMCX9oc5l+xS6lUuUsI/fmN58VA3i82j4jEB/+oPyQ6bxsiteGXkvIz4ONxh2Ve+FmgxaDNLJNH0RHnJ7J/mJARfn43o264ONq7eTt2HuOgoP9UIsOPXuDmlBo1oZ0zxWXUEST
sshPrivateKey: AgBNgdWFu4x9CEgKftriZCnjSPNgV+RpAxi8/N0XawXATbQEtsMHfctTsRtwsOvaEvCehlLq7dJWRmPiuTGZ7fA2vaetoj7ppa59kHJtEO4teVcU5n64glLV5EqZbwm/nel0b5ri+otrY2zjo/tomt/7e0K30OZbe3Wghr6pDH8MrScAxO+VqgJHqqXeFayY2cC4e/mZXGu7J+FKBl3si/i6nKtKFhaqCKF+Wn4tou7wZDIdx4A+jWUxTnQquQU8IdaZof0hUNKrmmD1ayK5mJxfOBLqt9TXoTKGwkZJkdr0Pvo8tx1DqTCfpyL9Ur7B7H4G5x9n+AaD6+Iwn0eohty+NPLoy3v6JYYayZb9Foi41EzRHm+W9Cc0TcDBNTN8kR9u713n+YNuFHCaALx4iR28svfKHHjsOEzZ5688WXuph2+eLm+DW03obRDGJYi25j9URYX31LJSTMEs0rhhsUn3eeipUjQ2Fdo3xXFnALLOci0XjRd6YvuR5DN1pkj4KFcMzHvTkyM8jG9eTHGloW55zFTNtb9QkNz+x75RRpCmNlT0bKqAjxfov70K8gbSIzZ7d77iai68JQ/FnTjuL8Gx2Jyr/ltlVnql4fpv1u691SCCTvoz6aAVknn63SXdrnJ9hv+/Nb01MZhYAciYgvAAoxAOT3UOpIBnJ5h/VUacJ+Op26jtSsWrMjAtVSN2RjqzXAbVe/HONpE76vEfDHKizHEUriT7y1WoOUxcbwGIFAzSDM2OidUW/SstCNCmGQ9Whdq427X+eQIh2h+EpCPYXy3YTuWfSISgkA74bSVeX+2mHpwgo+5GPfiAQmHajdZLYTo+GAaty7VdNWTL1AEdgFkYR4TwaeR8DcLe03efg5Q1SLd1I0+kjtkVQcWsnTnchnMcXK1tvOAcNc9MY9pKNh/gW/nAnsAyfEA9opl2LFyywpeDizDdowO99meiJB7+ISS1lOI5f7xvcM6ovEcTugj4Dwv05546Zxb1ziIGbhq5kiD5FIMCPzBo0vX4XXeUa0GRIVCHRxgygi17bTMwKGKRKGusduBVbc/+OKaX5ciAtxPSq85Z6XCXSEXzajsIFkasQtm7tt44U80fAlwmgAfGdRQMF5CcEW7x5WyXVb6mZUt3GgQmPdfrP6kTgaxXGTkGsbb5bcsbf3DeoAdiJ1rJPBVX4z10cJsdRCKI+d0VwVLu59eheOajdvPxdhR+tNhJfavaUKtj7THrMJUq7WPZq4GlPQpO7S3+Q6V1e6eaSw==
type: AgBf+Wb64fdsnUEwpxRcWg8fRtMi8MisZOA9gm/uwA2u+OZUNh/vHO5hzjxG3NVeT+6sOM3iJJhnLHTv7nxykP//3qG/PqkVPPYPvGm5v8sd698Ga146FsOKQzga7mn8vGZ2FYyZmfq2l3ZBS2j+12M6TtXw5a4dqCtrVq5FbvBr9MBKTxpHdKnpgddxyWYxE9uSgFm+2IrT9hH7OAhke0NrQQwbDFpOI/y2xhgaIGp/E78G+8Ijwc0ofrd8XIjKWcesXD4uQqTBHZfcoanMWlSqrfXDxSRiBNVkHot1AnE8DiukL3+Dntxr6bh3bIZWvsHuNa9fQnjH6BNskYQSYopreHdujsvYDm19vqHNpkW1mTmjdimWgxX5Ot2kzcnuguA4Qn2JjpLicT2GbNIEcI6rjghTM6CATOFWmiF933uIXhDN9RlfMR/+hTLisFvnZmdsH/6byvoe39UxavtVEKsSPCa3h2FN5O2HKrccWrAZ9fQdDtIzwVMXi48rsi5sB05By2WetsUMu3JdRlM8noQ3qtrKzdtjyQIUFJcIxRkL8mKz5lkXPKjn7qlItUY5VG6dnhHQRTxYdAxn3jKMpYh0jEtloI3m3SEXhfE9JPbA4RIgNi8evnRBMKwBwEr8mU52DbB1J3gU7Gm9IOYBsOgq0U1BkzqCqIaThoUWXCNGkAG3rc7uLuyOaHHVEBLsmu+m1cE=
url: AgB3C9i/Ue7oHmeoLQCr8BVYIqYIT8EmrhkFjj+vjBSTynYwoFonaiPMqDJ2XewJV9WpO6wL0IYH/cK0BkhbPhjN6oQ88Qn1MkuVCjtGht1cUeOzRtSE8hdqoOKaagFb4pcgwwm8ldU7bry3KioXnz36YaU7wcddUkHVQYQOp6DvcxtjonsLUFHecDcW+qL5aVzwI3CBkn0xMrc2kRiO2q2ZjR7BNNddNUw5f1v3fYNE0PARd+bVyegSEMuBRoh1amautWEfuYoCENPqhfd9Umbs9eml/j86pj9mvkVZ2CzRt78V+C3ciMZM6QlLV+BdiPm99/igyvZScDEB/tUrMaj/K6qhMYG7r8jNBQrsg3luvTVFcC75e8aSgfRJ0QDiYmtZ5fP+8AS+l2pyRFMkbOMAk62LCqAiXuXk/vC412Lrk4NkEIxqCDnf771PTOkbXYXNlHzC2w1AZSkvPqKjIvJbLCkCxGFVBeUmlZzcCIYDeXTpIRwZsVIo+sGpjbR8soeTb8suw6GqYjgXPhXm/S1kpL6yAXfCFCXmyxntMZCuWuFPPWfwzn5GDfSJS7Hz43cuh7vwrQ78xC1wQRs7EObWNUbLIfvWEypvncYmixKY8+02SrDwzcwADimJKMxs7Bp10rkMO4HkpcFDisVyHCgBbfG3BYFo0sT5+FN6b5Bhuo6hUaCzEe9HAeSIFNuKaX5wYbY1MzsRoWOvydStoQa9KwVeZwBPzZV0VW2c3wMRT1pNN4s2THGlphw=
template:
metadata:
creationTimestamp: null
labels:
argocd.argoproj.io/secret-type: repository
name: forte-helm-repo
namespace: argocd

View File

@@ -0,0 +1,18 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai
spec:
encryptedData:
anthropic-api-key: AgAdnPQzl2SNSqyAZkoBdEEGEjXfpMJUNMg4e040vxri4jbjoTnmwQ/DjmmIGtoInjhQA3NZRUKcIuDrO7nGMXFOPY9tSfhpdaAMWTji1CVZvmdJnrQwZY1LFBwgbozq3RSZZQQcZd2EPrhhsU5UlmacKk2WpuQdq76twfdU5WVD8xalxbt0H5UDvihuQa3sYX/rUIDZK2Th5IfaFqYZk5JvaxR8PFwBh9IOooGewWXWrsxVT510lxHRotSDIgKt/jl8ymsVA7IpddnbQHr6K77it7BxR6fLvDGaTQeQW0RtPZwsLvB268OTwp338RQ2sjmVuUDErWViLuk1V6UFmj5vhRR0X2dy/EipkWEJwR0Zf8JCW+vAXJ4NFiVFjmOsiTXoLHCYp0k01EwFcPQpnswKSdaQ3tuCEa4Q+rHu2DiXyfSuZ1MnWnTyLSx9H51VYUgIPwQa+BUVoVnAswypw4yLQHrfLu9TSvsNl/OonP0dSmgSGaByTc1RzwpvHBUAaOYII2LuuvkF90oA4cChGkGe1KuYKHFqiBdaT9jrsfeBUpgqFAH0uH9DNjBjU5EjlUtV3bdO7wXDHct9EOBZniKMH7cIbXfT2TBaormeHNqFtXxSRXy1szDpXiDD1QDoAJx0ylrwwqix7XPBlqSt91OZ8N1VPSUPvfnTQf7zirVWJNckU9fW6tsnQ+GDOOEwERPEAhqxWuw6V0HMab4VzpbGgWuvtQY5CsI2bFUYWSZAIJ6H8E5ip3lb8ilWtYadhx8QZD3aKi7k5h4bFWd6M0rg0uj7bfbW8YXauTS6Uz4b9FlvgrAPkmcWG84um/jsmP6a0YhvJDGKHMHoUmY=
auth-token: AgBq/MM/+fqi1H6L5o/xFeKYMfcn6+bmbkepbq97O0Ob+dFKWuyfXmzqM2Dha+NKpXcu47qSBbLpaOdLS5ONq7JDIcZ2byW0ae9khBuL7k2mCa5ZPh96LS46c+v1qNJb6F4NQe+jWSx2H9LIShoihom+lRueBT1/uthW3hnUUUgQMgXU3NYimDNAg6JK6VgX3yKkI7ePLPAet7+ykaL1aPXwcfCAldcobPmls0vxMQDtgd+o4nLqx2sKArFplnwZ9G1SEz2dRXNFm8HI4HhgtOBdv1ISjXO8XGRZWnFqgX3s3BqcwFcPqVuFGHo+2ZvAUW+mdkuAEJOloJEoZXJkFCP+l7/yMV7/FmFAhntqfdTwijEJ+rP/sUbUNSzn6BKM2ITRGXEJkOeNq4xfpgS16AOs+O0DWkIAr0kWCJG+mkn94U7ALCGYQXtKLy0g/87MJ3IwaUU7gowVawHoDmpY0QoAYDJwpbIa9yMnlPOzJANkc99dl85sbLeQMvkkb4RqQlbMc80gkFFhbU5/0kKX483ylqhRi0b4ZjGHgl72s/8+FhxTDkSXJQ2HbrNPGlna+B7TAgxYZ9IS1cpw4wSNzSCk23Rt3STHnXuu/QXRLeMijXGBDFNJgU0JNKLp63JC1gftHcNhqRL6lxK1tLyo5+0Cnh/KJN64b1DpPXZnzdaeR25GQFSDxsymjLm2BFATtufRPJqeIT7tn5TRGTr97fH6LQv/LuJ4VnjS6WLsQoFdUhDuNug2LDuyLHjpKg==
openai-api-key: AgCQR+HezEHwiwFD1pIkla1Byzz91SBoUyaWVTTuBrBDq201KvlLmqahjQwpKWs4YvqhmKkbF/mXKuMSyFut72IwH025tfmzzmTryOD6bOQDxI0Ws5NndnztzKv2qw2Js0c+6lAOJR+lEuo1ynGlG3hfS+bGShShvLZIDcD/hDa8IfhQBh7fKyFslJ52KxKK+zeEFDVQcx6Lrq/zP9IPH2QXz0bVQYlRyrQ4vqONO9h2pjxNAhDxuJ5FPk+Hb4ClGjyRPHFT34ZYsYdvHUgkjL3nOeaOUrc0GGJOKYZChfsK/JrqX8iOqgbE+0vQvMX+q8Tr6Ynr0KRKMcGPr6ak3eCNknJaZ1EABtkyUS2u5TzKqMzYq5DF4kpvPApIvaQW+VS05zD8piTQSZEFNRwwXDpyru/R4MmPvwt7OdSM0SJpQ8kR7tsuRgBPFY2Eduk6fhrDmMVgHze3AoRaqyh87CeqR3Z7/XofbdgZBqEp2vwiY425ArB6yMLpVvO6eB+yoTgCJ/UpfOxBDHE1M6U3goJhk/YeOy9UpTfRD1VyA2OuzJliDNtC784wxnGAsOk3qKk1bjjBqUzwITK41mOWqNLKCn4Ol9lXZtbbqLIiU4lXv4Pl93mVSuPLOIXINLGMRFl0rlvDICEjrrfE+JmwKx6i45sqsk3/hz1Fb+/bKFo7pl9JnAXvr5WfDW/rflwBrokfJVeYsSm6R2yxGWlosrKZIwIRctOut1EbbylurI5pIPp6vHguyIvZDDxwj3Qk3ewU/WVAcI9989aLR++UrZ5FGXt3apMk8BY9bq7DIQOUtgfPO98+cVPdoZSIh5bhQpCHAVPMGcGTdIPkuQbxSai0FsP8ipdHx0fluI6p/4v1Rv/BqZH0X8dnwPQoqUhSD+C575w511Fc8PEzgaG63S9hHx5Azw==
ui-auth-token: AgBgKRwErv5MtWIdqSN5BSQ/deHFE4469gzqL6gwp/3JidtBEiAAyuOIbc9dRCWuJNcdtixKOZT1rKuI8O/e6oJ6ABGAx+ckDfH468VKsR4AJhdZGcxub5j79NKccbNA5MKTnqxlU05zukUOkgw3wdsOyuQr3RnfL31GFbEVyDMej+Xr7Rs0fWrfqpasCi45/PEi8fkofMg45kGc+LMfDgovNtkKla2MAXwYlUk24SA+lgCFoG2DHB50GdTK2byCks4Px4J3jQqqHdLUfC+/gyqimPoVUsVGb5SY0xeNepKfTz7lHEkmDOB9R1wfv7pUaLMapc+eVrVN7reLHKOA47IEedDpnlLm/fyEfPe7ktkejc59tPyIyxBubrN+Vb/+6bDgi7xsXy/N5uD/2sPqWVgI2D7veWOJEjM+GYOYeb2iRbf7KlrokCXD/gRMiQBKqYyUdjWx40MjrhxzPArOa+cL0kb1CUgwuyjmqq5hNIUO2wo6/kkeNtUeT7r067LE5rtMoZG2EAfw/xrL1CI2F+3c72JzRd//aClt1MsSfPPPoFz0TlWSSc7qNDhYHuQf//G/Zd29Nk/Ac52HVwdkl2AXJE/GIFdwi+ypqK5mjmSUTn/JVE0ZaA3OpHOF/01meISuqBo+HuAWCV8sw6en0F8AF+DF8rw8t5hDdexScXep5QuJmm4CYgqThyjz2V5xME9oMmGA+Yiu/MJ4AlkLowijpXkKEWpEOBRpRRhu2q2szhZqmx1oKJw+qJ40CQ==
template:
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai

View File

@@ -1,17 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak
spec:
encryptedData:
admin-password: AgCbGhJduOlTEmA9OLys+ZmuXLU5T1dmdm+fRlrNnPtvjpDCmfKYnOP4rbvBs+Kvf1Y1TZ8fbR2UTISg96pXfY/TsSoJlU024NpBWSZ4e7HxJgHtq0ZwaNi+ruw41JiQ01LrGJ23q34WaPVFV/VfCDaWMlcIJ4gfRb9+lb8jXB6/sr9zSVNWtfrPoYdECcXvr3sOIz9Y5D1eiV/uuDuDQqm+vKpZp8a3bvWO5sB/uQ6a+ECCI/NUFJHtEP8sGLsCGoJMrO1UotXCvKLpIflcxMwW3NlzHd2e7vEPRdelpc3tlT88wd33lQ2LuXMJpQuMI6eHIcVA72UtTqD5bePU1HVWb6d8Dlg6UdebUDIphuJ5rsV3uKYNMgOxn+mvBVXhu5xRBsyanwHoUIKcvvuXaN93YtXHk4khgmt6GNNFdLFEZ69wDB5tPLrlpepvKzNzFYwEIRngYNQlBT3rn8677XgBhUltoq4vYoEqYredVCrh5N7R+DrYD2OYmfsB4WWoMrmE1+Kn/HA/Nu6i5M3mAYXg3NP6Yv9dI5ynMQKVBtwGnFn/hDym1cixmxdQs2FQ3ZaD62zzcXQ0Sc2CuiYVtKg8MZjLCGQoV6ZG7UrnuZ0cyzKE2SGmOaWwFHkRysXoRZiQ/eBybUWwU4DXigczSJ3zBEg57pjEsvloHqAEcGjMxqV1VqXpty/6tdFKkRvCgO6pH5wIQA==
password: AgBeVrYU5CDY8yBA0huXHEJuxykggruULsQB/Q/2sKOZTGZ9KyF1WSB2qJXG9CPIuwBz1YR/hlg4ezF7dQRaWNJspLXeTQgAmjwvgsa3XrfQEEoSFbt9KuN2T28W1S32YbQQJOQHMiMjn8+6YtIWTVM/14MWMGIw8J54Js1cWMsOS4BDT7qe059YMbivxOuPi+J0duL28doowmy5A393804LjyBTzWynB1XqmeBd9V5dJWeQSRl7jpvORR+LKGm/JXF9dX97kyeews0o89qa7VnPqxb9ZGUXhSufMEC8qmm2tXgyj80HaP+PE2aZ0M69o7Xmn1FkEaIxQ8stTyepG4+c7iySbdeqIWPWf0LpgPlkdEuQ7hXAtyNd2TLnD0AxNBiN8bKl2DGLIxaOpoa2PvVHqU08fFQuyHGB3uCvAhgO69Pvb6q62Og2hQHgNM4WWAgu1h9K/F3R4DyzxC0JPYAjfw+XIZCiuYX/UYpjEFXIw4pFU/2SCDGbbPNMKFVxppuBU8h8m7/QTawkJZ0y9ivv0SuIG1z2ExjX73dWk+h1my732wyaoGV+os4R5io5O+aznyvFUsH23N+jNj6WzMSromdYxHKIdmBakd7FiNqsXFy0vm7+aY+tie9WnB/ysC9zN13UpHTCkG5mn2E0X+ctzL9RM7ylQMMesfmk5VCnzewEQloGtEby334CCc/HH1X8+Ws9AO6okEMGplVI53cI2rXwXncFis37qO9yMeV5EZ18wfjXeaKxouiv/zZRuY4wnaojqJKIh0wnbwcOK1f8
postgres-password: AgBlBR9F4xBm5B31+dwptBjxH4LvjOBd/kDr5AJ2CRpjBZOsEqZa2jnFjUujlNFKfRDWDdzt3FQ6E7VW8Qt2hsrngDgRcIH6aPoZBOSZ0xVMpqLiY+Ek8YlZXH86Oh3+m4wUFNtcM9lZwPGU09/byC5W5zWhpmsypd+waXHF6VJZmweayupvbCwHOL2af58y3HXdl7uqX2/VyTR6mVZKcLVePO/QHNTMBlkPlfw+HKjDuluPcS3MirZU42k6E5OGo3v9XheF4HoiiY5Z76YkBI9k1shYzQYk2w48iPg7QV4heZxUDgwcrkRdJA/eEAUQejakTM4m2yx84ef1xzW2vnnkxz/OTO4KCZtcM7LddBOuO6TS9LSb7JEcfx8SJ7+oQTVhEk0e7nEAMGQQrcOE8TOX122ejBnMkhJra57VnHQ73Xb6fEjQy9dhz6pEnukagIvKKsZ1gZwJkY6qBfq4MtE7nGRCL5OFdXjEFwjdHG1pyR57sYqwvmX5ufE0YZkymdxdmFl/Wigh8uIeZAkfwYgH+8bWg/1Z5yLYzXxhx4S+YjOfLO4ncy2zILw8LnIZvnrOCWgogiiYOJM32E78jZadmZOGVVXbgRvOPLZpw8458Frwsk9oaXMjL7TEryXsH8U8NzVeUBOnnmW9Z6w25a755Jkb2AwnLYbfxMoHhJm7DTTERNM4ZM4umPjUEsXhlTRo/rJ8oE4El54sdSEHIuau0U+ZDbfE0V0y2YhVK1gx2iKeDdvPmGp2LGRURT6AQNc/a5c3/S9MXLEgVSiaSUO5
template:
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak

View File

@@ -0,0 +1,19 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-backup-s3
namespace: gitea
spec:
encryptedData:
AWS_ACCESS_KEY_ID: AgByl2/ljFIHzTD0Vy7srQoXRfdJRLG+WukgeLMJeiJm9MOFJBNEkr5ju2DemDNdRcViXQLN3yxqT/L0fG0rz+kaPQtLeVFToqr58vokxDasHw4WVIUOosi6wE+yaI16H6vxvdV8dck09nHE3fdBcwctlvjqsY7mKvyx4tYKdGRDoeJ/C7shYoDTl4E/ZtsSRkfOQ4Ojm6M6FU10zn03OOKrzaOUczxnqbAyGNFrZvCUGG38QQVnyYm/2HLofQgheSSQx8p0w5IgPRRhBM3IAyCLGkyEA8qHXSO3J5Y2m1izAoU9RVsHAVUMVTfYEdtUMADkDcywKzi0bi0ehBxDs2PuC89Uz4s6rKQ/v8xU+jUf3KogrZxsbuCh1iFVO/NCOLvQhLY5wU/946wl1WmS76/HxkM/D9Iq+KF0VsP0pJAA+SIyJQ3Bh0a9GnKRsJjCfO8qX0M0WSXhOpjw+4DvBoe653mV7n+LOEjQy7LJURFaz1HzQColelhhlQ5tHCN8J1jtjscFNiHZqyzBBm226X3oxr0cAC6e/l53ohNkKS3NA5aM/wouBrscs1/CfmDYKxujLyGqontFRQc3rtCZ29829u/RmuwietIVGeu+ooCacSM73zDqGYKM7HRr/Y7QYxuW0TiSaJhYMZQqsC4uo7ebZhRa2bWbCTHiVCs3jDdpSRyPgEECOvnOJbkTsh0e02HtrUEx7HBjLZua9FD2sskr8C9XJQ==
AWS_SECRET_ACCESS_KEY: AgCYxgmto8ytLz8QMm25/nIqqezlWWennhbPSPMB/aDYR+zW45LvAZbjwVp6wwSR/U5iXwfdZg5/k8+8CzGAKDjxc3Nwygih3cUpqVBOl+uOzD3W3oNDsyQckhmNA4jidwIbJF6ecV8O+GVuU19+E4QrkHTIP9lN5pnhkfIR7nMRVj4jdcNahH2O75huadGQII4GG+rmnGX1012IAhknq20CiOCbby3a2yHaU3om0srO1TkW/67jioQX2IvgUh7jVl6c4r1Y6b+glwV4bHc9GecDqNEF6uj6uy8ChNh1khRfUYVysIQRM9m1pV/qlKiUW/wjDZOjoW88IAg4wl2MMOFQby27jVwQWSe+kUPwRMf7HSNWoq/DaE/z71cMsdeEnAXtQMGwNzOr4EGM1n/faPGDWkj306l1xjoXNO2hLCNX8BspSBxQDWWADBGClC6C1AQX0HlsZLV0G18VCEkjTwvPRmPqigxzHganxWiWM0q3DfGrc+JvnGFW0r7waoKI5vIzxwzCbb3I042+3z2vsvo7ZW2mez+eKgeD0MvhRW2SlBMiE62MGJQL2BvTew1iU0Xean+19WGZO7PPysnOH6kU098kTJ5GjQpxlI2C+w6QC18q8eQeIvVyd/7wH+k7RMCDC+No6MCcYhDlcQNbIir6JJ7vIOd3n5NKXdg7Sy3SnjkyDPTOjXTwyn2hHkATMzUxgn/0frNZYSsEMTuoNlOfcZLr7UbFv+Qlr49rkAEMo3deohfGiQSD
S3_BUCKET: AgA6ulIpP/DrYOQ7iqo7CSeaSj0L/+PXDPZ7SxPmdu/wrqbXw7nlxAyp+7QHqkUub5XgVhwrk3KPmqUPcECPDbHdt93+nlM3PVD0yPNkVaijncEPRVccGu/VhE6Nae5lXI9U3pnAVAXn+7z8iwRpF/vr1qIGyQwiizsKyfBQhvRSupzOvY8sypbItDjyjttxlwMRorGI94GObeUS89kSx/MB7BWZJMtUuRRSG6YwgH/XIkIWbo2p2LD90EhNtaT9rYa1PcGP9BVcgHf/9zVCI4+1LWbfmZSgobwAEKLhzZfhzCM2DhN31CsVWhpp0x0gYmspNbtQQvoosKmeBPBT+BLkTabAhjx00rvVX3J48Er3PaVAjw6JxT1KSdaUuCmcIzX3O8ys/8PNacaEgEiqmeuIPgID8YHSXSfs9RIUkjKBWGydjE90lMQPgnqOBkPWTd1BNRqHj60D2pFp0/h7+j8/OfBj7doDp9ECwcdQqjwzX4pNi7WQiGd+Ri0/7DK1xSAOL0lwgg8VSrqCOIdasAZRVQuHhuWwKMyhdQyQCr+zCOQ/bLQaPeF1m7tKxFfU4lNz5tRiC8AOQI5aHX2gRrkpfugD3G9qFFQMl9EPCdNeBh/ezVWxSxekWvQTuGJ2WnLD33BhsZVLKjXa+tHjD/BsQjQgdCiqv8J9gPgtngx/pAFf0NaQezVU4tBfaYD4tetcrZDz5UtW5tTHaF4=
S3_ENDPOINT: AgBfKbxdU9hZrxIpbM6b+hDthXQ+uYrjWHGmxSdjGvxgHB2P7E+HqblPAHjIpiAsGiPESV75ZKs5/BdEBOoZpbvneAuRgVLZ5mxkWiQ35q7sJpWaUg47icnlEPFoFj8oxYbi1NRAYB5hbc3AU0s11mw7wre0pRZu0pgSidHk/lyuSXOHKQzuhXxKYmV61LjMxCQGwDNwbiDNuSZyU7AZ2r+vr2W7Tzu6G+tJctwQd3HOnYbOLMV4tBv93nc7EkU4tbdvdIvkGHEmKf4r4F+nGvKZ3fZie1QKyQvG/4+i8OKqby9XJtviEEfBqfrk5qb1dNQlqCfA4ThQ11MmRiP8VoaUp/yoUHHYACNY9HLBp+N5Cgbbcxo044U1c8b97I6ZOZJ2waZ9XkrBpYPPXWJRKxLeNgYoJqn3yMZV/U561DO1jLZ2cwQXXaFrm1WT7VjcB0czdJHW3FcOg9lzYKMCCTTX+cD4M1oK992931eECQxBecrtlQYD+NlJng8ARm7myTACOZGYMQo2gjdM4ZBh9KqoCT2jrFC6E29YwfRAIXrhiWdZZxOW6Bu9Txt8FgxnIlSz9iZ1hvbfdvrSZTilJbAAULKFqLUgNpQbdgYHtGXQkzFHqYmbdZ0vJ6taIli7y+/Rz6xKcql8uJLxnuncLvLvXHxXl+rWeKrAMn+jPvnuCcdq6yVPsI0Nz/B4EQRL7Nzl9XYQxSybAJACrrCjgEuHsquoPpuznlGuk2scuakXdWOzMg6i/MEk
template:
metadata:
creationTimestamp: null
name: gitea-backup-s3
namespace: gitea
type: Opaque

View File

@@ -0,0 +1,18 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-credentials
namespace: gitea
spec:
encryptedData:
key: AgAN7QteEqfzTrlCRR4Hi50nPJ6GGktFqOJIMgYvAL+es3ELUy9KLGtcyaC+Bf+b9lmG5+UgiCdz8jWbnVI7xEhDKXx8+3KzLw3nPAhYPO3j4XSi3FhbHwE0p5IGlZ0/XdXP5SRr/X0P6wQewapLSClGoya/ErY549ORfL0KgbcUrPE4ReSKbER6R/biRzSpkngxljf0rw4uruhNTlmBvnL/0nRSdZGtww7fLhcQceARutGihKHjOTkzmv0d39hHJA+4xNTi4PCbfOUJBkKwriy7NTi6AZkGRH2b06m+/IGfYYvTp/2gdtf/omS0ZhIoEVeRLhupiwkRAlclOSk3EXRsCcMiH8Gp5Mp4lUuc6yYHwM9ONAbq+P2OlUTjNDOntgH4Z6p02D3pIUf6nterSTuga7haGr5Ph0OCYBk/V6KkswwuTsSBAv7EI5TXrZoALp0xxXBXS459sc21UCgp2K5Xdhb3K92KwtcQDND7qAdIVZXAgf1wahm2svjRWhvjnRVLqChbb8MvkEQC+queHvQvIW+/5mCEb/hAEY/nAHJqTqQhsHnpPxzMhLvxN+7S2zvqSC48zSVAbKvwNjJcaiZ3s4dTjgCaryT307Gg4FI9bHixtk30fB9ZUkMu1cm51QC8ld5CBVKLMi8mHSezshiZ1n2PVYMiOivbghDZQxJ+uutZVOlrF65uAtgnQgmm9q/fTD7FWQ==
password: AgBdnjk041bBX2rwz+q1G6QDk+QrHfvuzoTo1pCRE1w7NLACFgGwzeSTS7gjPXwtjIkgganpZYgT9ZwWK8+gcT6Sae/CBTcDPgE6Y7FtfpG244wgp+mn891TDQXONW/OBAYk+706YDbHfqqz5AQxKAEorJ74RZuLTAo6kB85A/eveEnQqq4cByJaInz0eprB1A4hGfBLF3XArjiVdL7f4X33mR4scH80GozJiqUnG6ruVv5jcEBXnzySVrnCaq4VgmgJTu5Lsjz+AyH7niGTGFIsVBeOnVyeodKwgDk4N3HeSj4w9No3ileZELD5nLZh+/OqdQHw2niBz5/hPjDyXw/p96PfLM3CFygXfs/Au1DqdDf8ccZufhF9OK9CCQqlIgCWK3W7lhsdhcgK65XisRG+hh37Vcxke+A9ZoLGMjIT43KejTqwE8r3wRxPrmejBUcVHohiD7L60dFbcUp64hSBktcIGwEAWn89gmD7Ar1gCoCUmFCBCTVD8c5LQ2W2xeeZ1OrtDpMkOustzXUkzqkqiCB6TPMqDQ4J5FXCyoTuHrBnieLWaKL82kdUohF1+Jak3UGe0rSpOxBmoNC/17yVXyiwBGOUZZP+DJyvwiJu1jc7j3USQ0HRCcR7af96IcwGxge4yWbR268oI53yiJlwh46JFcc8RkxQQaq3Nh3RoiGJohqx/or8JOIFsXEtUW4WSLNQa2tZ4dxRI8F83JnBCo2W+zvL2yXKRP/LLMXfF7nAOuXPrYKrRcdnnRhP1cdDUSIQk8c6gzvKJrsEkt/j
secret: AgAR/5Tj79Rk4fgvPwvRU63q2xF0fokEe0/KWQG9CGiYWwut8wB8PlST5WEcKWIjwNfGpSsyHRQHywIVwDT3m+3/wxfQ5eOtlHIfMuglSymqeLHvj9tuM2EWabd4o9frKHHJigNojXS42JZ1jVSW+PeEfNrGFBARWWaDYSA5586/M8U5qi6UOBIkdyG+796wqqIeUM52d+/lwHHcETQOdhwa5/B4IrbRB33CSv0UHmfM+MeNtUWEnza5+8tm+1mPFyBJ2N9isXxMeyHXWutwOJVsAjEe+X35mC1C03eIsDjpmpjQ+hweUhinu6zse7CsIwcBDuq4/+1xd8xaQ/lmYc01FophuAcVRqDZIhlaAjCLI7gCfX2fliluVuaUbZ/A4DY8Q/psOteODeI0kVEMCaL13T1j6tZZWyDqDAB91xKFh2ne4DOOTwkZLP2StMt7rAENy/eMld+VNrfNliHJUVJGMoPTzCxPaZQxJfRYIbpE5uGGA2fWkteuX5VJRqZ/8JshdBAUVG1XOSXlvM1zlW9KlyvO3j3KhekKG7bY+NndIwFMNhujG3FGWCA+k3MYQfAks3FWhW+ZXA0vYZpnPOJuYO5tqqDO4GLsrdJc5GaXMC7kcey90PqkwzkwhpxX7/X414w3bHWoYttmFCcQwSyA+S029gSFBIleNTKkjpXsNl8qB2NDJR1lf2kufO016WP07PB1NK0hGjvI0isz0uHZILrAD0i55t858/EjWsD2gg7jK2ww69vVxi2c2o6k370YVv92qoZCayTuYsXF0Ay6
username: AgAmO891YOV8ZtWTNjOF+ZA+6yG69Jdvozq49/XCQabuVXSqoESWA/V/6RPL8+up8dFa+c/u0J+s+Pu1cJL5zjZmJtXStOIuhoIGBTkWBPgHQ11hp/5apkR4nt0gWR3fWQtz1v/073WsaQGhEyKHB4ZsxAXbwEQ41f+TQzdkvxu2DT07Kc4O0jN+QsAl/YtPLk2YW2WaiY/Q7dDq5+LKAbKVRR4Aa03OuMVDapPGOEogu3wC8vl9lkYMhPBrIosq9ycqk4roenKbtmFSIkzytLOLRKSNEmPUSklDMrO8HEGSByASxJP10jgUZW9wY4lE/DmsjuS2CQsGLXhZAye2S3hgGyewnIA1ZfsnJTb7jSsnQ0BVxm6HL9lKy/1PqDzMuE0Ou+LHiIt5DLbCrDmn0F+n8jhNlpCbtiZXVL/Tvo7SAruBe858CYXc2StFHNi5hA1dRP2CCGklY2Wmi8u5JeOdi8RXdhw8deZtI0G+I1NltqzhXdDR55kMWahy2BTImJrGj8owrdg5bFz1Ne63P7jiS80BeUfDKffYAWuslDEiKkhO8YSoVj7ixeWIfXvZRyaecaCMcZ+VuNlZy3R5CbFTcIyZkfotK0+aA9S/q+iM6K36ghTxpQv6WSecnHpeR8mkVckCGVo+iiCPIFfQw2VwOHn+BtvbH8pfr0McKoGHAvoDGGH9sjgDbjAGwCYFYr7qHTKaFc1vbVcUNw==
template:
metadata:
creationTimestamp: null
name: gitea-credentials
namespace: gitea

View File

@@ -0,0 +1,15 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-runner-token
namespace: gitea
spec:
encryptedData:
token: AgBZkU9AgXmFoBURd4WImEFFY4k3LTvGmMJ0ZEZETYzQNS9ICyM0TZfK9nF/bSY48y9t2Vd+VNNW2WJhqec05HE9rLe/vQmo2L8OlBLbCpFL9vCEFJ/h8AiWdHGsLJtCH6tOJwBQ667KPfAGFeYZzKf1XF5HA8O6G1+v7kO3mtSNSIYWq7S/swe5QO+bgtclkeDjg3xgOhjYTzlOsg+rcTknhwGa2YpL22rFN6Tbf94oTK6xVBYrkly1nhiALGYyGnROSv/Ua8+M4xEF5tr0QslwjRmzaQTC+Skt1HKXogsWVzE03RZHWQpmcz1ltaKfikl8WQwBmOBDeg9xNRfBu5n5jli9NvdKZgMEIyQQZtp27QCsIk+FLxbAb2Zy/hG3G93Jvk9mRzgryXyi0te8w+iEP1WtVOYHSyLmk0AMCahn2hPIfzQvlOXJxoSFhLbwm5eSwtqYTQkQ2WFKmwaznSZBUmk1a+LvxV5pOhPMvc52w0v8u13ng/YzDDnyjY/6WdYX5pJtYOGUFDt79yd2Bd0Hc4CH9y50dy84YfdlC/prgTVeVfoJFW48qhE4sNkBhL28PjgkcYmIOb9fskP1o3RuHIoR28y9ipZMkzdLimCRzIQxf4GmdOlGsoEEvujgiGr1ta8ilRoAjjDAD593PuYELFRfGYjk4KGNkz6MP3t4UDRlG8umXMQIqhEWUaRGytqMnfrPGiGPAGCBfAAes3mooDNO/NO+WwNq09fd1OWFeDvzJMOLxWLy
template:
metadata:
creationTimestamp: null
name: gitea-runner-token
namespace: gitea

View File

@@ -0,0 +1,19 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: gitea-smtp-secret
namespace: gitea
spec:
encryptedData:
password: AgBMuISQeA41mtBIOo686sND2EO12Jv7BIogL5G7xxt7wKfIk88dQrU74vs+fv3OtPce2Ra63QkR6po31M9fZkoiYba5yqJtEOc6em3y0ZxM/UzavbRvHwrvsWEYqmHBnkCUjcJijdGbSX+rHyGsXLfZO0gOjqXO379Zl1fzmV4p3F5REXFQm6HorVtWX/LOSRj9GDW76l6KgPR/A+CbtAx8Cq0m3D3fyiaVLEReP3uOgBOo70APHj9Yp56EcgOtVa1pEgCxR4ctXeS4t/EliWcHc/JT4TBdRBRDYPKLfME6FvJxjLjaSZcWxtJrJzCv3+vA5LlfObuHY31aSDRqYwO4VBCPhf3Aa6Z5UXgUnmAtJRhHa9pKSSjW48jgNb1jDPIkQn5XgB2/twJ+gX3inAkrTQ82JJ75Rz7XWC8KmYkOtkgXgU2buCa4nIfPeXOr5qvutyywxV1Ge1nK0fQYneQZVFXlHTbAQXBJMpVvJoJ+G3xGjm1904/iBGkVKmNrQwaABUsGBC6ZIHGOTa45GBqrg3ODU2Gr61SCYxv6m3pMU1msR7QYne0oqLCVD8mLDaeSeiQI4ZY9u4ddsVwM6l2BFrT6+3IQuYPBgOoodzDVlCgmA7hoekhpak9vZ0loSHaWDXdNt75SemAjsQfwCO5sSEkr+wbCJEQpXh5p38RMZKTuOh3nYEGQEx/MQNl3VD4FarK/zOJM9EO9IkqdM4LnqVo3zPX4KAPosS1PPKS8
username: AgBF6MiaI1x2xQOUoF4NUh4MeFF64Db3vywcEO0FdJ0U9EirVFMsBSSiqJLy8ok43ha72s+/RLBNHiSSRKX1UMWwwCsfs+LQJNh9EetgHRxoyqkHiqRMX5V2acU2scdPE/FCFQFOYzAjweup+kP8xNu1WKuDtPBRiAgBNDfW59ihFi9TgOJQ2AnDHottjm5CNaWsbTOSgZrXqzCEChfHu5K0W9cty9ENHYqnYDfcm/zPLYeUPW0gVN5GJq3lPo9vZjM7T2JnwryjOkBaPCRCzHOpRF3bMrArFrjbUlH6gdI0APf4CzGLMMKol/jTMG2tLBseaNQHfbz6p1vFYExCL60gSN46fzh10zIWaIC2O+SgoLLOizkQZWf/v86cdRerBSl6PFmbRUO18XUQ4SyR/WPM71HD1jeLnUZjKtkOu+fqQKlv8kBSELHGqiURNYDnbmUA1LQpdNkDnMkRS+uzQ3XwWCSQBAn+u9wh69kg1oPVEN60Nc4KpNwFIg25aycGkP3cMklfl3/u9nr5KruwtJe+hl2ynSk8zeEFWWQrBki7+88CH9aWVW/GTA8Ho7Fz+gp4ZUdUA0WhH2LRAQIN945pvJIkHm/AYAhH6pZiXdBzYeguPY5VEf6hDVM1sa39aSZzs81cj0YHxbjR/BoBxbUAa9xW7JYH2rcIqXDhJB4zq2H++8e0eABsdQC3tMmE1eQA51d0yg8+2fX+CRkcvMCmI3VjS/mdirrtnctv
template:
metadata:
creationTimestamp: null
labels:
allowedToBeCloned: "true"
name: gitea-smtp-secret
namespace: gitea
type: Opaque

View File

@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: auth-oidc
namespace: music-man
spec:
encryptedData:
client-secret: AgAKQ24qP43Dopfb6LfH0o7RIQjK3IMjII8knmdpfL5ldXJRGaYYYQDjZJcIE5HiLoEFXKkLHm39k83l6sJ1fn1RCbbskSGmosYNenJgxOqTZdlyC911km1PVMu890N2JEqUVRQb4YTn8+1dYUglWPL+zaAki3wJ84G5uM6ZY0R7mMEp/5J8RFEeByzdTiHLtB9U2fPnBJiGdKfFN/ysnoZOLN0nAHvvnTEa4pkO2BXNrrhVl7Nje/y9IDmQntCNIHomF9Fy4bo1SpChj1vRXWbLETlMdFkRINHMVWAvemoIDv+CaHVRaaoy/XtTngqA5lyZZwiwiix+HBIDPE5HAnGE/Zqgc2L+ByR3ak33H6O6iGJoVdR79uu9PiW0iaWJwruXgSfm7Fq0KuOHNg0QzfTqsZf0jgT5RhT4apSGHZOl2g4QUkMUee6BPuFM435ZZFdOfDZw1YRdqdFvI6OJgx7+G+kwwnEa125LuBgbJzqLRVxH0LbD9HoBoEqZUB4H0V+0PUY7uVSKcpZUi0RwiLjcoHJzaJlgJdaEc9GrDhh8OvDXOxsBJcRrJDmAH5WbGLXbq7+HUknfbJFkcl6rg9bPWDah0DDf98weqUdafpn0TTt7qoOGW1z27yVApGn9bm9B7ykCa/i9QtX/6G9RTaoslTU45pYzA1Clw9ccRNxv7O+2afU5KZORZPYNXCho4K7tpR7Hw788Rj68x1GYxrZh82+s0fs/SYpvzvN6EiFxKY8R+f1azefD
cookie-secret: AgAPJomNWVV2m8izVg8CQ78mnXP9qQaXh1WaIELv4JzwyUIcMOddDDq8PWzmAiWfV24iYj/oy9QB0jruI8+78H5nU6QsF5KB3DlZiDjzwaUeNOs1q4wPsQ/aU9JygYpWD2dYUn4pBYqQQxJyf/gJ1u6jbGMbjsO5J3kkK4RaFiTNt+A3151nmYVgWToknTPPcTaFgpH0xumhQHicO9MyaNsSKOJ1c2AeUiWHqc/0a39tuchCgq4Gm1FOKjXXfevcZw/IamnRiEegoAr5qgSPl7e1xfZeG49FJacsfpWEuQhmKGsaePTHok141VnkKHhlmFdewe3Sn+kSWJbtAx6jky0sqClUKqopVjxfj5IKwcucE0NjxxX2HXmQb/e9nY3BLQ1CRjQQVCNa+JJy6DvAbcERHs7t9T7ATXm4S5lawp9fp8UB1ySy++q4Qvmc+4/16tgugqVEths/n+gSYLWAM8Ibd42RhNXlWqjzvpAUXmHX3Dt/26wNy+5ZnRlcwOIotPBdDqQKlp3fGKc2loC2JHcyN1PH31EwY5+h4pdU/sGSU+EsPd43hALUHq3XzvibHVDQTzyerJgC77NX6kvu2W+V5iWN9LYAAVjMh5yzLYjfgoC/zzIiOIe9yRf6zJzW0nhSkdcoC8EH0lcwEEDp0d/CuO0A6AwWOBaer+tnQtjHeZse9RdKEfyisJDDa7xUNj4T9e8Z+shdkU94vladQpVP1lyRd4auXF7B+T0+1Pkmvac8wDHDk67iomi+1BpS+/2jdJUGJO+91+qIdCFLPi1Y
template:
metadata:
creationTimestamp: null
name: auth-oidc
namespace: music-man

View File

@@ -0,0 +1,19 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: renovate-env
namespace: renovate
spec:
encryptedData:
RENOVATE_GITHUB_COM_TOKEN: AgBliRXcjCrWeZKa3A0RuQatvluoF30R8HEAt7aVJwSf9EbMtB/ozbtsbHxYTsyfpdnaewD+BMRbaDdQOTpmlPxfK5eqKFuLXJGCXmiiMEHvK3GG0y0STjfgC2GyrXvtmh/lg/Awod9ZneJ2+Xoi0X7ZIngWwHXmgvsRfczKtwZAZm/YVDLGHRW2BCwBgzStZquopxYGQRa58WSx9XkE/cV8UOb6Kw+4fGMKggJPr8ERUQyermCZ0HBOpkQwPpWoPhRFdCMj2qasVLttkK/jLG2romy3JZ59RGrQ7bj2p9IdJujqtteWb4vDdOc4jvplKWlB8QptZ872a2MPeyvopqkCsmnsfi6tup6uBaUzE8Hym0olpqK73dCjc4RP7x64KwsdGOyi2aJgEz1/Ro7GMu7sam5sdpTjpdA3EbyC8tZrnlC51mUlq4ghP5XELGj6DjvLWZ3dk0UOSQ4vQ/KLGyA29VBgvV3MdabpGNhs5VlU69p4RofxoFT9n9CrtJN8FPOnMUK6L58Zf3w+eYJQMuyttgf/h/stQUx6OU7R4d/4xA1j4YCFRxw2mXaYO9Tjsxsq2KB1v3TzyZKtZfYLeiNhv/rAfPs7krACs1ncNXVtS7sP3G14+Asvu6EdnlM86LThBmIeKRW8lSry5pO3IMOIzNGrqulFnuTGP9fy9cs5d58q/E4sUXoY0Liiod92+1RO8dpyn+xf9afCAei8lG6TFNPWdLzcl8WOJgZLWmRdRNhezOmSZHxnh8P/Ul2oxhrVZSYGLhvIfBVU9oY+s7lgw8FpM08RLdP3OsKigs5/uiE/56qHZHvj2YZf6mg=
RENOVATE_TOKEN: AgCV8DmDz1CT+wjtghW9ERy9tY/GCZfehPbd3vqikGx8tsZzxEoKukJzkeRJmsHqDTXylFMZHB+saYuk+vWOeMT7oGs/6wIC7eC08GN4OcBYpihrTSrzoAkp1WwLJV0KoNGbtbgoQXdlPJOWBjs6X90mrFmpwRewcaADks/uS/ONZw3MhiSbukYN3L45uL6b6gUp1Yl4nDyCyvCDoRpr1FQt2MMltyChimUtLXoMrnV3iO0oFlFMITJRjvauYrKXdD0xyWxji3Rbrowbgr/iFkz/0fTpg6AwhP/ISRWtO+NDB3kZ6/CvcK2a1phTIgf2eiHfvMQIwnpfRauyUPtNHkCInC7OOtn0eyHZmAqkSaJTN1rJN3X8lRIgjhtZr9upE6ZCLK5kMdq87oDjtvwc0X0AyeTxV3EGXh87Zfda97KhG5LSC3uShzc8AYPA7SGxMTuoF4wON0Qa7AdAeGvtL+2GuhRkHhmyOeY4iORrVo8TlJsN1DsZmrbRArJWzvMcb31gefxTmg8tSZhuLOo4RHsUayCNIPmW/AvpcbGJS1pPXS8nPkDqSoeL6UW/G8CoEEisr50+9ztGOCmurobbadHcs4LhwAC2s9SuYuCTTSK4c98ke901ks4Iho8TuG3WmBkgJlEsNMigMsnW6pwlx/Xnij06csRY8Xcfh77MjluQXx833oKRxffwC9NuIgXcBG0cqGraEF6RFxKlu3DsWRIElt5Rnju/WSNhDsWubKrMVtKXuYX0tTBn
template:
metadata:
creationTimestamp: null
labels:
allowedToBeCloned: "true"
name: renovate-env
namespace: renovate
type: Opaque

View File

@@ -0,0 +1,17 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak
spec:
encryptedData:
admin-password: AgCPuUHRhzPEau3aH9UKq9gp+k5Pu+lPKizST30OWEOW9ozwdcHbq90eb3b5Sd+R/fRPkqta/8TrhZx3uzqNhVnMUSSwfcnxG14Nfq9yl/CAIuXBrPH1C+SghLWrGcv/CaKwT1arTk/ECjtz87vfpGXwqjjRc3rnH1fRjMTsIL6vwOLKIad6dXREzviUGoyxWp6U7A9QPUQqAApRY3zF5cSH603TYiSd8+EIEVViaAAQDg/2xaknbGsMi6M9Ei74IQIFoYRHarMnpkdn7AZnbX5u/bpIOmjMloUazVg8DhPHlc72QiRzqq95YFwfjMqJumOZwS/Fo4rQXoyKM5/aUe0j6zx0wgWk+MNHq7JUjdsJcGIrlgFW4Um9oDhZbYiexDmwG6cR/68fKLOoFpVx4gyZH7Is3+v6l/EYm+ssARU0e+28xA6ZVGm7YhsrgjLmPd3z3YmVafgFWpyEvIGkAKqHNmWD0txIZ3LWptISqwjcr7Yk63kPFjnuXS9t3nWOzSF/RXnLgM90ZSkoytZU+CwyjAHFf30qRWeMvnXB6Ry4g7Koz54jpoMj0Gk9Fqb0v60DgOd4yxhTbT91Z83x2SvKeucnbDB8IK4xMK+QNfKy20BbCHLGfY5kCdYxIUMNlB0LUsjDOnGjqCrAUZjkeHNI04jNvHYMItgP4/EgTU7NB1f/GsP6cwwdwbKmpHoFKGBPIPo5iMUKK51/U/da/1Zm3f9Qr1HrKQ==
password: AgACslplMgI3ZXPpx7BivIM8LIhNDD5j38wbzd4H6IKqZab/nTNpmz2Hc2hCD96H4ljhCIiw1mETzCWshu3vwvCVtJnH2pXiVY0nHpJcSH1C/HqBPOXWVEuEcm2EgvKnf8koKdtkHtAaT6egWrpvNEHCnapo1pa53qB1cZk0PbPQ+cIcedVTa6yF7VzFcOQp56mbtu8ez/iww+K/SdiXtp/IkVCaiOsC7j5ZUwZVmPB/w/nEjgPPETezy3OsOaAokelKq77EUfrj4mh2CEtK9bHBRkbmZkTc9pVkKC/SuWRud5S6xV/VcLRdy/EA/wuqj+Z/XF3hOqyHUpNblbntZ60lqwxoju0ZK9hgZWwZyNvzz04/gv0TkX3/W5JTxNwemJ32AjQ8dQZOz6gdkTsX68lT5P4YlR58Dxsb3WxQxG1ishkDlxGCBA2oQSyyIMFULETuu47V32N8FXOy5fgWHO5ZSer5yFjIIjgU7GyywiSesvZ1m9G7lB5EX56ay1djEmUPiKP0KDyb3QSgsaVbUEdTDi0bXXyDgoVBZEgQNS6vttd5b4qV/IGDqDB4g2hw5xLiff2vkF/h0QPGEz61O+FDyYP5m2rR37o5lD6yWJGI6cB/epoFTKVANPyyGdrIDKtVlgEE30w/fciq+FdDRZob7Yg4uzXi/kGvAYfBmi0Ek04EIU1FLdEq8p+5yZhDSBy4TWjTuH5JTvYlwHjFhnLrCHC/zCBSgkOPgUkAGNOHdTya2ElqEGSSfcRrrvMhnY6ZlZubhGFSdPSrZSXTauSo
postgres-password: AgB/qqkiqoQ1d5g+ZMF5Za0ZQ0kpD+6sh/ds9/uc5XBD4VkaX2+1EDrEcq776x/thbngR1Fi3HNBafRtflfptO64N0r3GBJiakVIT+fEdp7tJs6VKjqcP9HFAEObAVEdlcJzOm9h8iPvhi+kxGgkJYSP3ojJ/lfoWoMLtZjsOJtlQH8yw0uPd8xlgVukssxUzFKkT2RkihLwlK/Zn/8MX8BNJT3W9Li8ruK7pm0+qKqsPKcq0hpKHVRDyYpWubAmiGtj8+Oe3qSYoQxepIdUvtEgEXqHJ5DFggxYB4VzVErdKcyn2tK8JraofcOYpR+JM+8VRH/HWTp0/zNJzY8LCgN/V+KkPkCghVDryW+8ZS2FSP/bcqhPWVbQE3s9EblUny8htsOu2GikbbQcF4LzBKDhIJS6hc9SUbw5sfT7CtREcU7XCfq4VyX7dQgOdiGotO8OTmpXFZtceX9E8ClGTQ3V9tKZkzlpILkEip3PjbkkLshNTtJaRsFgRu37PDddUHaVczBoPKBelONC+KpKBQDoiTfB08BylNxdROsic2ag3upR5avOuqf0YDU6tDKBP3S0FPhPoZOmisUsiZNc8/QqV590gSUvqNBRDWWbqLtYsHmgeYRKDH02kiEzIBQuARryOjK8gOxmv0oo1SVdOd33Q1smU/W5YQNqtoVcqRQOtn0lR/cfQjtHJ3OSSNBZuvEx0BMUsLkH5Oud6nmnVITxT6gxLOS6K/BgV8WyUwgaO0r1sFdjiArCQcaDTjLHkAopYjgWsjEkV04XdjxssEJB
template:
metadata:
creationTimestamp: null
name: keycloak-credentials
namespace: keycloak