Compare commits

..

540 Commits

Author SHA1 Message Date
danijel.simeunovic 7b05910eaa fixes 2026-06-12 17:17:51 +02:00
danijel.simeunovic 4fd694ec67 rename overlay 2026-06-12 17:01:34 +02:00
danijel.simeunovic 0fc1be3a8a forte prod 2026-06-12 16:44:44 +02:00
danijel.simeunovic 9297398d56 gitea update
/ test (push) Successful in 8s
2026-06-11 13:03:59 +02:00
danijel.simeunovic b0804e1e6a scan
/ test (push) Successful in 11s
2026-06-11 10:34:11 +02:00
danijel.simeunovic 8216399155 trufflehog
/ test (push) Failing after 33s
2026-06-11 10:14:25 +02:00
danijel.simeunovic a70f078bbb drop drop 2026-06-05 19:38:30 +02:00
danijel.simeunovic a24e61d538 disable slack notifications for forte-drop
Signed-off-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-05 13:41:42 +00:00
jorgen.stensrud 275ec675da fix(apps): drop dangling namespace.yaml ref (enterprise-apps ComparisonError) (#19)
0a98674 deleted `namespace.yaml` but `apps/overlays/upc-dev/forte-drop/kustomization.yaml` still lists it → `kustomize build` fails → the **enterprise-apps** app-of-apps has a ComparisonError and the whole overlay stopped syncing. Visible symptom: `secret "forte-drop-secrets" not found` on all forte-drop pods (the SealedSecret no longer applies).

One-line fix: remove the dangling resource entry. The namespace itself is fine — the forte-drop Application has `CreateNamespace=true`.

@danijel.simeunovic — pairs with your cleanup; after this merges the secret re-applies and the pods only need the right image tag (helm-prod-values PR #4: `buildcache` → `v20260604-200105-1316f7a`, buildcache is the buildx cache manifest, not a runnable image).

Co-authored-by: Sten <sten@Sten-sin-MacBook-Pro.local>
Reviewed-on: #19
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-05 08:44:56 +00:00
danijel.simeunovic 0a98674a27 not needed 2026-06-05 00:05:56 +02:00
jorgen.stensrud b713ec853c feat(apps): forte-drop web + mcp argocd apps (prod) (#18)
## Summary

ArgoCD Applications + Keycloak clients + sealed secret for forte-drop **web + mcp** (PROD).

## What changed

- **forte-drop** + **forte-drop-mcp** ArgoCD Applications (two-source: forte-helm chart + helm-prod-values).
- **namespace.yaml** — explicit `forte-drop` Namespace at sync-wave -1, `Prune=false` (avoids first-sync race for namespaced resources; doesn't cascade-delete on base removal).
- **keycloak-client-forte-drop** + **keycloak-client-forte-drop-mcp** — labeled config Secrets; the registrar creates the OIDC clients in the `forte` realm within ~2 min.
- **forte-drop-secrets** SealedSecret — UpCloud S3 creds (existing drops bucket) + PG creds + PASSWORD_GATE_SECRET. Consumed by both deployments + the pg-backup CronJob.
- **forte-drop-web PDB** — minAvailable 1 (selector verified against the live forteapp chart's pod labels).
- Wired into `apps/overlays/upc-dev` (NOT base → stays out of upc-prod).

## Post-merge manual step (one-time)

`auth-oidc` SealedSecret for the web sidecar is still commented out — it needs the `client-secret` the Keycloak registrar writes to `forte-drop-oidc-credentials` after first sync:

```bash
CLIENT_SECRET=$(kubectl -n forte-drop get secret forte-drop-oidc-credentials -o jsonpath='{.data.client-secret}' | base64 -d)
kubectl create secret generic auth-oidc -n forte-drop \
  --from-literal=client-secret="$CLIENT_SECRET" \
  --from-literal=cookie-secret="$(openssl rand -hex 32)" \
  --dry-run=client -o yaml > private/auth-oidc.yaml
kubeseal --format=yaml --controller-name=sealed-secrets-controller --controller-namespace=kube-system \
  < private/auth-oidc.yaml > apps/base/forte-drop/auth-oidc-sealed.yaml
# uncomment in kustomization, commit, push
```

## Depends on

- launchpad PR #17 (postgres + namespace via CreateNamespace).
- helm-prod-values forte-drop PR (values).

## Review

- [x] codex: namespace first-sync race → fixed (explicit namespace, sync-wave -1).
- [x] Keycloak registrar unblocked (stale chibisafe/minio config secrets removed; registrar green).

🤖 Generated with Claude Code

Co-authored-by: Sten <sten@Sten-sin-MacBook-Pro.local>
Co-authored-by: Sten <sten@Mac.domain_not_set.invalid>
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #18
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-04 18:47:08 +00:00
danijel.simeunovic dffb9c43f0 dbunk delete 2026-06-03 20:16:37 +02:00
danijel.simeunovic 33f0463c1f upc dev spec 2026-06-03 20:14:21 +02:00
danijel.simeunovic a997a6b81e kc cleanup 2026-06-03 17:41:10 +02:00
danijel.simeunovic 071f57f1d3 kc cleanup 2026-06-03 17:39:02 +02:00
danijel.simeunovic ecf871f0e4 kc fix 2026-06-03 17:36:29 +02:00
danijel.simeunovic 376d81a5ac keycloak client cleanup 2026-06-03 17:28:08 +02:00
danijel.simeunovic 428de7af78 tofu config and docs 2026-05-31 20:48:25 +02:00
danijel.simeunovic 24c59256c9 tofu+tools 2026-05-31 19:53:26 +02:00
danijel.simeunovic e319295f62 bunker host 2026-05-29 22:06:08 +02:00
danijel.simeunovic a7106bc8f4 new tls wildcard 2026-05-29 21:58:34 +02:00
danijel.simeunovic 6d874111da tenantID 2026-05-29 21:51:27 +02:00
danijel.simeunovic a8cc103e4c dns01 2026-05-29 21:48:32 +02:00
Ghost a9dbaf5354 feature/tofu (#15)
@thomas.solbjor her er "import" av tofu fra ditt repo med justeringer for å tilpasse patterns her. Også minimalisert til å kun opprette cluster, ingen managed services som postgres etc. Ta en titt.

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #15
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
2026-05-29 15:48:28 +00:00
danijel.simeunovic 6e175e9e8c docs 2026-05-29 15:20:51 +02:00
jorgen.stensrud 396c771f59 feat(homepage): list forte_drop in Apps (#16)
Adds forte_drop as an external service entry in the upc-dev Homepage portal.

- Target host: https://drop.hackathon.forteapps.net (current Coolify deploy).
- One-line addition under `services > Apps` in `infra/values/upc-dev/homepage-values.yaml`.
- Will be retargeted to https://drop.forteapps.net once the K8s migration ships (spec in forte_drop repo: docs/superpowers/specs/2026-05-28-k8s-migration-design.md).

Zero risk — pure metadata, no cluster mutation beyond Homepage refresh.

Co-authored-by: Sten <sten@Mac.domain_not_set.invalid>
Reviewed-on: #16
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-05-28 14:04:05 +00:00
danijel.simeunovic 0582cd9917 policy 2026-05-27 23:23:21 +02:00
danijel.simeunovic c49d03d7f7 onlySSO 2026-05-16 23:04:11 +02:00
danijel.simeunovic d47dba2ae5 signups 2026-05-16 22:12:04 +02:00
danijel.simeunovic cf9eb47ecf script fix 2026-05-16 22:08:56 +02:00
danijel.simeunovic 3eca723f05 diffs 2026-05-16 22:05:02 +02:00
danijel.simeunovic f36996da11 script fix 2026-05-16 21:57:44 +02:00
danijel.simeunovic 6bf7db21d0 registrar error 2026-05-16 21:55:44 +02:00
danijel.simeunovic 2641d55784 scopes 2026-05-16 21:53:36 +02:00
danijel.simeunovic 117297effc sso vw 2026-05-16 21:47:59 +02:00
danijel.simeunovic fda90f9e01 adminToken enc 2026-05-16 21:34:34 +02:00
danijel.simeunovic 1124377d97 adminToken 2026-05-16 21:29:14 +02:00
danijel.simeunovic c0710b89bb no signup 2026-05-16 21:15:38 +02:00
danijel.simeunovic d7bda18aea domain 2026-05-16 21:11:17 +02:00
danijel.simeunovic 2796e1b9d3 name 2026-05-16 21:09:04 +02:00
danijel.simeunovic d7a0c26117 icon 2026-05-16 21:08:36 +02:00
danijel.simeunovic 693f2f9168 homepage 2026-05-16 21:07:29 +02:00
danijel.simeunovic 2509ef062c domain restriction 2026-05-16 20:58:00 +02:00
danijel.simeunovic 957757e557 host 2026-05-16 20:51:44 +02:00
danijel.simeunovic 070799da05 bitw 2026-05-16 20:49:25 +02:00
danijel.simeunovic 1a2817e537 domain fix 2026-05-16 20:42:17 +02:00
danijel.simeunovic b47b0035f5 smtp auth 2026-05-16 20:38:21 +02:00
danijel.simeunovic d3fac4d43e smtp port 2026-05-16 20:34:22 +02:00
danijel.simeunovic c37bd3ef04 from 2026-05-16 20:30:32 +02:00
danijel.simeunovic ad661ba3dd allow signup 2026-05-16 20:27:36 +02:00
danijel.simeunovic a9625f96e6 db secrets 2026-05-16 20:23:58 +02:00
danijel.simeunovic cb64edc927 cleanup 2026-05-16 20:18:48 +02:00
danijel.simeunovic ac1c242fb9 kust 2026-05-16 20:17:14 +02:00
danijel.simeunovic 4b29c07fd6 secret 2026-05-16 20:15:37 +02:00
danijel.simeunovic 52732626e5 ignorediffs 2026-05-16 20:10:19 +02:00
danijel.simeunovic 8634436dd4 StatefulSet 2026-05-16 20:07:17 +02:00
danijel.simeunovic a8baa169e9 secrets vw 2026-05-16 20:00:22 +02:00
danijel.simeunovic 73ef3a6e12 pg fix 2026-05-16 19:49:38 +02:00
danijel.simeunovic 302705d374 icon 2026-05-16 19:45:19 +02:00
danijel.simeunovic f3286ef77e homepage vw 2026-05-16 19:44:17 +02:00
danijel.simeunovic 74f4f86770 vw apps 2026-05-16 19:34:42 +02:00
danijel.simeunovic f2c56156bf vw postgres 2026-05-16 18:10:14 +02:00
danijel.simeunovic 21fb50ba00 vw fixes 2026-05-16 15:55:18 +02:00
danijel.simeunovic b90b630b06 comment 2026-05-16 15:52:10 +02:00
danijel.simeunovic 66de9b8a0a replicas 2026-05-16 15:48:13 +02:00
danijel.simeunovic 716c552be9 ns 2026-05-16 15:44:04 +02:00
danijel.simeunovic f048b47a0f vaultwarden 2026-05-16 15:39:55 +02:00
danijel.simeunovic 66f40427ee mappings 2026-05-15 15:47:25 +02:00
danijel.simeunovic 332881cbd0 fix 2026-05-14 23:47:14 +02:00
danijel.simeunovic f363afa087 browser flow override 2026-05-14 23:43:40 +02:00
danijel.simeunovic bc42347cb6 gitea+ACCOUNT_LINKING 2026-05-14 21:30:53 +02:00
danijel.simeunovic 80d7bff4bc groups 2026-05-14 21:18:17 +02:00
danijel.simeunovic 3644a3ec87 mappers 2026-05-14 21:14:57 +02:00
danijel.simeunovic bd478478f1 fix attemt 2026-05-14 20:40:44 +02:00
danijel.simeunovic 67b1d95509 account linking 2026-05-14 19:39:38 +02:00
danijel.simeunovic fff95d98a5 remove protocol mappers 2026-05-13 23:15:28 +02:00
danijel.simeunovic 8b743efa43 KC fix 2026-05-13 23:13:09 +02:00
danijel.simeunovic 4ca9039686 kpolicy 2026-04-29 12:54:07 +02:00
danijel.simeunovic 6a9eadbde8 vault ignore diffs 2026-04-29 12:50:10 +02:00
danijel.simeunovic f19f7c9237 icon 2026-04-29 12:07:04 +02:00
danijel.simeunovic 5a459d486e dbunk-demo 2026-04-29 10:53:35 +02:00
danijel.simeunovic 31fb476a78 row 2026-04-29 10:06:02 +02:00
danijel.simeunovic a088425b70 homepage config 2026-04-29 10:04:20 +02:00
danijel.simeunovic b3b3edf82c no header 2026-04-28 23:03:15 +02:00
danijel.simeunovic 308755a4b3 layout 2026-04-28 23:02:13 +02:00
danijel.simeunovic db6afaf180 vault
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 22:44:57 +02:00
danijel.simeunovic 5a2f9a1b88 Update infra/values/base/keycloak-values.yaml
Signed-off-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-28 19:27:38 +00:00
danijel.simeunovic 1c6f18b67c homepage 2026-04-28 20:38:59 +02:00
danijel.simeunovic 7132f5000e docs 2026-04-27 20:35:27 +02:00
danijel.simeunovic b4100bd456 mm ns 2026-04-27 20:16:06 +02:00
danijel.simeunovic fff117a500 ns 2026-04-27 17:40:46 +02:00
danijel.simeunovic 03c75fc4cd mm ns 2026-04-27 17:40:05 +02:00
danijel.simeunovic df73c4bdc0 mm sync pol 2026-04-27 17:37:54 +02:00
danijel.simeunovic 6a7de704f2 enterprise-apps 2026-04-27 17:34:43 +02:00
danijel.simeunovic be8bbd2c12 aksapps 2026-04-27 17:33:47 +02:00
danijel.simeunovic c469ab44b0 ent apps 2026-04-27 17:28:48 +02:00
danijel.simeunovic 290c8b91f8 db pass 2026-04-27 14:05:38 +02:00
danijel.simeunovic a776bae4bd image tag 2026-04-27 13:00:37 +02:00
danijel.simeunovic 7405ce27dd chart name 2026-04-27 12:55:20 +02:00
danijel.simeunovic 1281e8ef37 databunker 2026-04-27 12:54:18 +02:00
danijel.simeunovic c497c54e8e fix 2026-04-27 12:28:47 +02:00
danijel.simeunovic b57459cf85 rm secrets2 2026-04-27 12:25:25 +02:00
danijel.simeunovic e8dd213685 rm secrets 2026-04-27 12:24:14 +02:00
danijel.simeunovic 1d879c82f9 secrets shuffle 2026-04-27 12:21:50 +02:00
danijel.simeunovic 94c8265475 overlays2 2026-04-27 12:01:59 +02:00
danijel.simeunovic 17d7c4a655 overlays 2026-04-27 11:49:10 +02:00
danijel.simeunovic f3dba72c5d aks-dev 2026-04-27 11:33:24 +02:00
danijel.simeunovic cc9c9049eb ignore diff 2026-04-26 23:55:55 +02:00
danijel.simeunovic 9f6c5105af netpol all remove 2026-04-25 16:04:13 +02:00
danijel.simeunovic 45e502d74d argocd tls 2026-04-25 11:49:17 +02:00
danijel.simeunovic 167d893233 clean scopes gitea 2026-04-24 20:18:02 +02:00
danijel.simeunovic 8b9ffee242 socpes 2026-04-24 20:14:28 +02:00
danijel.simeunovic 4069e255a8 org scope 2026-04-24 20:05:01 +02:00
danijel.simeunovic 3b1f498616 Update infra/values/base/keycloak-values.yaml 2026-04-24 17:40:15 +00:00
danijel.simeunovic cc47bf6b9f grafana access 2026-04-24 15:49:47 +02:00
danijel.simeunovic c1d61398f0 SSO grafana 2026-04-24 15:45:50 +02:00
danijel.simeunovic ece4a8d199 grafana tls 2026-04-24 15:39:46 +02:00
danijel.simeunovic 03c47ad109 remove trivy 2026-04-24 15:24:58 +02:00
danijel.simeunovic 3095741590 clear KC scopes 2026-04-24 15:13:58 +02:00
danijel.simeunovic d7ba859e61 no openid 2026-04-24 15:09:10 +02:00
danijel.simeunovic 07eb9b7051 optional scopes 2026-04-24 15:05:07 +02:00
danijel.simeunovic a911ff64c3 kc scopes 2026-04-24 15:03:14 +02:00
danijel.simeunovic 9e13560e5e basic scope 2026-04-24 14:40:32 +02:00
danijel.simeunovic 3d84acb278 DEFAULT_EMAIL_NOTIFICATIONS 2026-04-24 14:25:29 +02:00
danijel.simeunovic fde81c6ec6 dbox 2026-04-24 13:42:52 +02:00
thomas.solbjor 8648269e55 Update secrets/base/kustomization.yaml 2026-04-24 11:25:38 +00:00
thomas.solbjor 84fe4cbe7c ts-mcp-secrets-sealed.yaml 2026-04-24 13:15:00 +02:00
danijel.simeunovic 38158be0a8 doc 2026-04-24 12:55:50 +02:00
danijel.simeunovic 202e84badc doc 2026-04-24 12:54:26 +02:00
danijel.simeunovic a6df75de93 dbox 2026-04-24 12:38:50 +02:00
danijel.simeunovic 4f4f544100 k 2026-04-24 10:58:20 +02:00
danijel.simeunovic 8d4b6493a0 mm 2026-04-24 10:57:53 +02:00
gitea_admin 8505481291 feature/multi-cloud (#14)
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #14
2026-04-24 08:48:53 +00:00
danijel.simeunovic 65598c9297 karpor diffs 2026-04-24 09:47:52 +02:00
danijel.simeunovic 3f0f70699b karpor 2026-04-24 09:43:16 +02:00
danijel.simeunovic 06522b2f19 ts-mcp 2026-04-23 14:44:33 +02:00
danijel.simeunovic 4c65035485 ns 2026-04-23 14:11:45 +02:00
danijel.simeunovic 84f4bebc08 ts-mcp 2026-04-23 13:41:51 +02:00
danijel.simeunovic 5394b2c714 ts-mcp 2026-04-23 13:40:33 +02:00
danijel.simeunovic c4e586a7be ts-mcp 2026-04-23 13:38:47 +02:00
danijel.simeunovic 1fa070b041 argo 2026-04-23 13:35:42 +02:00
danijel.simeunovic 9c905355e3 argocd known host 2026-04-23 13:28:34 +02:00
danijel.simeunovic 6b1115ec28 argocd disable submodule 2026-04-23 13:09:02 +02:00
danijel.simeunovic 2fb276a62c ts-mcp 2026-04-23 13:02:00 +02:00
danijel.simeunovic 3efe1b68ef auth doc 2026-04-23 10:05:15 +02:00
danijel.simeunovic 5df104beec sp 2026-04-22 13:54:51 +02:00
danijel.simeunovic 0ecfee3cf8 prompts 2026-04-22 13:51:38 +02:00
danijel.simeunovic c88938adb5 feature/ai-review (#7)
Co-authored-by: gitea_admin <admin@forteapps.net>
Reviewed-on: #7
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-22 09:30:02 +00:00
danijel.simeunovic d05a16840e pr trigger 2026-04-22 09:11:40 +02:00
danijel.simeunovic d7c7242aa1 submodule 2026-04-22 09:10:38 +02:00
danijel.simeunovic 3bf9fa7837 pr label 2026-04-22 08:48:05 +02:00
danijel.simeunovic d2596568f2 version tag 2026-04-21 15:17:52 +02:00
danijel.simeunovic 2a3539350b AI-review (#6)
Co-authored-by: gitea_admin <admin@forteapps.net>
Reviewed-on: #6
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-committed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-04-21 08:20:41 +00:00
danijel.simeunovic f97b613c12 remove unneeded yml 2026-04-20 22:46:44 +02:00
danijel.simeunovic 9c7db11470 remove unneeded yml 2026-04-20 22:45:53 +02:00
danijel.simeunovic 723072bd1e cleanup 2026-04-19 13:47:29 +02:00
danijel.simeunovic 046b78446b add opencost 2026-04-19 13:41:44 +02:00
danijel.simeunovic 56a1b49d10 missing manifest 2026-04-19 13:39:26 +02:00
danijel.simeunovic d557eb1865 revert 2026-04-19 13:28:40 +02:00
danijel.simeunovic a51ed84124 Merge branch 'main' of https://git.forteapps.net/Forte/launchpad 2026-04-19 13:28:03 +02:00
danijel.simeunovic 73e253a579 traefik 2026-04-19 13:27:59 +02:00
danijel.simeunovic d7c1341eab don't sync users with cron job 2026-04-19 11:43:47 +02:00
danijel.simeunovic eed53006c1 docs 2026-04-18 23:12:18 +02:00
danijel.simeunovic 395ca70c2a prod values 2026-04-18 23:02:02 +02:00
danijel.simeunovic ea04ec20c9 remove docs wf 2026-04-18 20:54:48 +02:00
danijel.simeunovic 03a0d7c9ae feature/multicluster
Deploy Gitea Pages / build-and-deploy (push) Failing after 5s
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@trumf.no>
Reviewed-on: #4
Reviewed-by: gitea_admin <admin@forteapps.net>
2026-04-18 18:14:00 +00:00
danijel.simeunovic 72a65f0e06 client cloner (#3)
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
danijel.simeunovic 44fc242ae8 doc
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
2026-04-17 11:43:50 +02:00
danijel.simeunovic b2f601e950 doc
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-17 11:42:46 +02:00
danijel.simeunovic f8b17cc030 log level info renovate 2026-04-17 10:59:52 +02:00
danijel.simeunovic 6639d0e3ff renovate prs
Deploy Gitea Pages / build-and-deploy (push) Failing after 5m15s
2026-04-17 09:58:52 +02:00
danijel.simeunovic 4485731ab5 smtp+starttls 2026-04-16 15:57:59 +02:00
danijel.simeunovic 439b8516f0 smtps auth 2026-04-16 15:46:54 +02:00
danijel.simeunovic 0eccd2d439 smtp auth 2026-04-16 15:43:10 +02:00
danijel.simeunovic 3e1029a557 mail notification 2026-04-16 15:39:51 +02:00
danijel.simeunovic 61c2801e0a smtp 2026-04-16 15:32:10 +02:00
gitea_admin 8902a0e51e Merge pull request 'SMTP config Gitea' (#2) from feature/smtp into main
Reviewed-on: #2
2026-04-16 13:17:28 +00:00
danijel.simeunovic 4486279eab smtp config 2026-04-16 15:13:18 +02:00
danijel.simeunovic 020dfeffd4 client secret fixes
Deploy Gitea Pages / build-and-deploy (push) Failing after 6m6s
2026-04-16 15:04:27 +02:00
danijel.simeunovic 7e10954a8f client secret bootstrapping
Deploy Gitea Pages / build-and-deploy (push) Failing after 39m32s
2026-04-16 13:55:13 +02:00
danijel.simeunovic 88c29565b6 smtp 2026-04-16 10:42:35 +02:00
danijel.simeunovic 87ee0588a7 renovate pr targets 2026-04-15 16:33:58 +02:00
danijel.simeunovic db8a1de797 10x repo PRs 2026-04-15 13:46:13 +02:00
danijel.simeunovic 177150e069 gitea protocol mapper
Deploy Gitea Pages / build-and-deploy (push) Failing after 7s
2026-04-15 13:27:14 +02:00
danijel.simeunovic c63a9242f0 renovate loglevel 2026-04-14 12:44:47 +02:00
danijel.simeunovic 1d43ecddad renovate daily and more mem 2026-04-14 12:26:46 +02:00
danijel.simeunovic a702a16155 renovate token 2026-04-14 12:17:53 +02:00
danijel.simeunovic 8b403736a9 secret renovate 2026-04-14 12:14:43 +02:00
danijel.simeunovic 0e8524b84a renovate
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-14 12:05:14 +02:00
danijel.simeunovic 58ccc9fd2e Merge branch 'main' of https://git.forteapps.net/Forte/launchpad 2026-04-14 10:53:49 +02:00
danijel.simeunovic 08d870d44c oauth fix 2026-04-14 10:53:45 +02:00
gitea_admin 2b7d441803 Update infra/gitea.yaml
ignoreDifferences
2026-04-14 08:33:47 +00:00
gitea_admin e74a8cb9d8 Update infra/values/gitea-values.yaml
ENABLE_BASIC_AUTH_CHALLENGE: true
2026-04-14 08:23:09 +00:00
danijel.simeunovic f5166b3797 readme 2026-04-13 16:08:30 +02:00
danijel.simeunovic 18fb0ca3da repo names fix
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-13 16:08:01 +02:00
danijel.simeunovic 4abd528b19 launchpad 2026-04-13 15:55:25 +02:00
danijel.simeunovic a9a3e0e8ab Merge branch 'main' of https://git.forteapps.net/Forte/launchpad
Deploy Gitea Pages / build-and-deploy (push) Failing after 6s
2026-04-13 15:54:23 +02:00
danijel.simeunovic 827213c883 migration 2026-04-13 15:54:14 +02:00
gitea_admin 02d5b3eb5a Update .github/workflows/docs.yml 2026-04-09 09:11:16 +00:00
gitea_admin f90833711d Update .github/workflows/docs.yml 2026-04-09 09:05:12 +00:00
gitea_admin 1c6a0a1b2f Update .github/workflows/docs.yml 2026-04-09 09:02:58 +00:00
gitea_admin 665e4020ba Update .github/workflows/docs.yml 2026-04-09 09:01:37 +00:00
gitea_admin 643c0aaf9b Update .github/workflows/docs.yml 2026-04-09 09:00:24 +00:00
gitea_admin 61184f6fdf Update .github/workflows/docs.yml 2026-04-09 08:58:00 +00:00
gitea_admin 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
danijel.simeunovic 279bc8b273 traefik default time span 2026-03-23 13:11:28 +01:00
danijel.simeunovic c914498590 line format 2026-03-23 12:27:58 +01:00
danijel.simeunovic 5d8437bd01 filter logs 2026-03-23 12:24:27 +01:00
danijel.simeunovic 684b35c009 service graph 2026-03-23 12:16:44 +01:00
danijel.simeunovic db8fb09fe1 sm 2026-03-23 12:10:31 +01:00
danijel.simeunovic 7a204e367c time ranges 2026-03-23 11:55:00 +01:00
danijel.simeunovic bdfada0838 opencost disable ui 2026-03-23 11:49:30 +01:00
danijel.simeunovic 7269eb3121 title 2026-03-23 11:42:25 +01:00
danijel.simeunovic fca94cde94 fix 2026-03-23 11:41:13 +01:00
danijel.simeunovic 161dc52d4a refresh 2026-03-23 11:07:52 +01:00
danijel.simeunovic 76b39241c1 oc yaml indenting 2026-03-23 10:50:44 +01:00
danijel.simeunovic f50f03d8e0 typo 2026-03-23 10:44:46 +01:00
danijel.simeunovic 4266327d35 rates fix 2026-03-23 10:41:21 +01:00
danijel.simeunovic 8f71d159ff currency and rates 2026-03-23 10:36:02 +01:00
danijel.simeunovic f8ecc54b86 panel 2026-03-23 09:39:34 +01:00
danijel.simeunovic b1b75c77c5 cost values 2026-03-23 09:37:44 +01:00
danijel.simeunovic c2aa680a0f opencost grafana json 2026-03-23 09:23:16 +01:00
danijel.simeunovic d0ab490eb5 datasource 2026-03-22 16:01:04 +01:00
danijel.simeunovic fd0e578131 opencost scraping 2026-03-22 15:51:11 +01:00
danijel.simeunovic c6bc723b8a opencost scrapes 2026-03-22 00:17:12 +01:00
danijel.simeunovic 1983c80f15 opencost 2026-03-21 23:55:33 +01:00
danijel.simeunovic 5dc12cfaa2 new api token 2026-03-21 23:36:33 +01:00
danijel.simeunovic 258ece5f85 dot ai secrets 2026-03-21 23:32:32 +01:00
danijel.simeunovic 2f88b2d16c svc graph fix 2026-03-20 14:41:10 +01:00
danijel.simeunovic b4ffae5078 service graph 2026-03-20 14:31:52 +01:00
danijel.simeunovic 7aa69f6a7f cleanup 2026-03-20 14:29:32 +01:00
danijel.simeunovic d394dfd55e host fix 2026-03-20 14:23:46 +01:00
danijel.simeunovic f728f9dbd3 Tempo doc 2026-03-20 14:22:14 +01:00
danijel.simeunovic 7522b88cfb fix tempo 2026-03-20 14:19:55 +01:00
danijel.simeunovic afb39f99a7 Grafana Tempo 2026-03-20 14:04:20 +01:00
danijel.simeunovic e4f8f2c071 traefik grafana dash 2026-03-20 13:46:13 +01:00
danijel.simeunovic 2ecd0c8a44 traefik metrics 2026-03-20 13:32:06 +01:00
danijel.simeunovic 3c81fd1e3a cleanup 2026-03-20 13:11:38 +01:00
danijel.simeunovic b665faaa7b sidecar image ref 2026-03-20 13:06:11 +01:00
danijel.simeunovic 5071110c72 repo url fix 2026-03-20 13:02:48 +01:00
danijel.simeunovic 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
Danijel Simeunovic 08fe2c447d FC token 2026-03-18 11:00:52 +01:00
Danijel Simeunovic 852b906bb2 mcpcoder 2026-03-18 09:27:08 +01:00
Danijel Simeunovic 5e62acf63d image 2026-03-17 19:35:14 +01:00
Danijel Simeunovic eb99e5420c kc 2026-03-17 18:03:36 +01:00
Danijel Simeunovic 66b477bd3b kc host 2026-03-17 17:16:58 +01:00
Danijel Simeunovic 070e3c9662 legacy 2026-03-17 16:27:55 +01:00
Danijel Simeunovic 1ccfb713e0 del 2026-03-17 15:42:06 +01:00
Danijel Simeunovic f615d32282 bitnami images 2026-03-17 15:41:15 +01:00
Danijel Simeunovic 5a801e0651 kc db 2026-03-17 15:37:00 +01:00
Danijel Simeunovic a294016e84 keycloak 2026-03-17 15:24:24 +01:00
Danijel Simeunovic 91d0b592ca fix doc 2026-03-17 13:16:01 +01:00
Danijel Simeunovic eacce3a8c1 mcp auth doc 2026-03-17 13:14:15 +01:00
Danijel Simeunovic fc2ab655b1 env sections 2026-03-17 12:22:19 +01:00
Danijel Simeunovic aa08a66dae mcp auth sidecar 2026-03-17 12:19:30 +01:00
Danijel Simeunovic 77bcb8fdbb docs 2026-03-17 09:53:42 +01:00
Danijel Simeunovic e71e408c6f path 2026-03-16 16:07:42 +01:00
Danijel Simeunovic 4aa212cbc5 imagePullPolicy 2026-03-16 15:37:20 +01:00
Danijel Simeunovic 57cab7d3aa loglevel 2026-03-16 15:24:36 +01:00
Danijel Simeunovic 10f4e4c2fc url in doc 2026-03-16 15:08:08 +01:00
Danijel Simeunovic 2f5eee3e73 fix repo url in doc 2026-03-16 15:06:46 +01:00
Danijel Simeunovic 7036cedfa2 secret 2026-03-16 14:25:47 +01:00
Danijel Simeunovic 93d3778605 secret 2026-03-16 13:48:21 +01:00
Danijel Simeunovic 865013a2af invalidate fc token 2026-03-16 13:29:07 +01:00
Danijel Simeunovic de36a44ee1 FC token 2026-03-16 12:20:08 +01:00
Danijel Simeunovic a77cd4d257 git ssh 2026-03-16 12:07:15 +01:00
Danijel Simeunovic b4aef11546 rev 2026-03-16 12:03:24 +01:00
Danijel Simeunovic 8eeacfbc0b git ssh 2026-03-16 12:01:58 +01:00
Danijel Simeunovic fae0826400 ssh access 2026-03-16 11:54:32 +01:00
Danijel Simeunovic 7aff19ccab fix 2026-03-16 11:45:43 +01:00
Danijel Simeunovic ae075bbc48 docs auth 2026-03-16 11:14:12 +01:00
Danijel Simeunovic d02da33700 docs 2026-03-16 11:00:42 +01:00
Danijel Simeunovic 275c100af5 secrets 2026-03-16 10:14:21 +01:00
Danijel Simeunovic f75b83977d secrets 2026-03-16 10:01:51 +01:00
Danijel Simeunovic 6b181b8d78 del mcpcoder 2026-03-16 08:30:28 +01:00
Danijel Simeunovic bd264fe074 new repo 2026-03-15 22:19:26 +01:00
Danijel Simeunovic 58a5c0c333 10x cred 2026-03-15 22:17:36 +01:00
Danijel Simeunovic cb071b96d2 mcp10x app creds 2026-03-15 21:50:28 +01:00
Danijel Simeunovic e56a506eab mcp10x forteapp 2026-03-15 21:28:41 +01:00
Danijel Simeunovic 63f6b34bb5 mm 2026-03-15 15:22:18 +01:00
Danijel Simeunovic e87bfa5abd musicman forteapp 2026-03-15 14:31:50 +01:00
Danijel Simeunovic d077300d03 annot 2026-03-15 12:25:05 +01:00
Danijel Simeunovic 350714fd3c mcpcoder forteapp 2026-03-15 11:53:38 +01:00
Danijel Simeunovic 0e7f6e94b8 ssh 2026-03-15 01:26:29 +01:00
Danijel Simeunovic acc2c4c8f9 repo 2026-03-15 01:22:46 +01:00
Danijel Simeunovic 47b170e1cb ssh 2026-03-15 01:18:53 +01:00
Danijel Simeunovic 9e9329d085 cred 2026-03-15 01:06:27 +01:00
Danijel Simeunovic 91f7436eb7 source 2026-03-15 00:50:32 +01:00
Danijel Simeunovic 0d5f66ce05 cleanup 2026-03-15 00:40:56 +01:00
Danijel Simeunovic 9ff65c8e66 path 2026-03-15 00:10:33 +01:00
Danijel Simeunovic 8b348ed5de path 2026-03-15 00:04:05 +01:00
Danijel Simeunovic 8da4c80fa3 change chart 2026-03-15 00:01:38 +01:00
Danijel Simeunovic acc8cf483b callback param 2026-03-13 22:36:35 +01:00
Danijel Simeunovic 0c9c4b6b0d sidecar oidc 2026-03-13 21:57:51 +01:00
Danijel Simeunovic 36ba7023f4 policies.forteapps.io/auth-token-secret-name 2026-03-13 13:36:47 +01:00
Danijel Simeunovic 61f623e3c7 order 2026-03-13 13:24:09 +01:00
Danijel Simeunovic 2e3232ecb9 loop 2026-03-13 13:05:22 +01:00
Danijel Simeunovic 58673feeb0 sidecar 2026-03-13 12:59:26 +01:00
Danijel Simeunovic 5e15b7e0f2 nonroot 2026-03-13 12:36:42 +01:00
Danijel Simeunovic edbbb52bc6 rule 2026-03-13 12:33:14 +01:00
Danijel Simeunovic 0a803cda11 method: GET 2026-03-13 12:26:44 +01:00
Danijel Simeunovic 4624cc7278 secret gen 2026-03-13 12:21:21 +01:00
Danijel Simeunovic cc7c5fea70 name 2026-03-13 12:16:52 +01:00
Danijel Simeunovic cbd25568c0 auth sidecar 2026-03-13 12:03:23 +01:00
Danijel Simeunovic 6d59897fe3 rw 2026-03-13 08:57:06 +01:00
Danijel Simeunovic 0989a6d6cc auth 2026-03-13 08:54:37 +01:00
Danijel Simeunovic 0eb6d8d774 ro 2026-03-12 20:46:18 +01:00
Danijel Simeunovic bd1945d105 write 2026-03-12 20:36:00 +01:00
Danijel Simeunovic 16580ea871 disable auth 2026-03-12 20:19:55 +01:00
Danijel Simeunovic 0ea45cd664 annotations 2026-03-12 18:27:22 +01:00
Danijel Simeunovic 235612bc17 host 2026-03-12 18:17:10 +01:00
Danijel Simeunovic 4825c540b9 ns 2026-03-12 18:14:44 +01:00
Danijel Simeunovic bd40ffece4 argomcp 2026-03-12 18:12:09 +01:00
Danijel Simeunovic bc94cd13d5 reduce more noise 2026-03-12 11:22:38 +01:00
Danijel Simeunovic 5c6269c58b reduce sync noise 2026-03-12 11:21:48 +01:00
Danijel Simeunovic a803ef7438 icon 2026-03-12 11:09:47 +01:00
Danijel Simeunovic 639278d485 clustername 2026-03-12 09:53:20 +01:00
Danijel Simeunovic b517a84990 del 2026-03-10 12:51:16 +01:00
Danijel Simeunovic 193b1aa28b credentials 2026-03-10 10:59:36 +01:00
Danijel Simeunovic 3b0eb5c1d5 Merge branch 'main' of https://github.com/fortedigital/sturdy-adventure 2026-03-10 10:26:59 +01:00
Danijel Simeunovic e06901bd7b mcp10x 2026-03-10 10:24:37 +01:00
Danijel Simeunovic 67da1be2d3 mcp10x 2026-03-10 10:24:26 +01:00
Danijel Simeunovic 135839c54c mcp coder 2026-03-09 13:22:57 +01:00
Danijel Simeunovic 0644998f16 dot-ai 2026-03-09 13:11:53 +01:00
Danijel Simeunovic 1a0d787766 ns 2026-03-09 11:58:35 +01:00
Danijel Simeunovic 7f1a842611 wave 2026-03-09 11:36:49 +01:00
Danijel Simeunovic caa3c2ccaf undot 2026-03-09 09:40:33 +01:00
Danijel Simeunovic e14f22f29f adjust 2026-03-09 09:40:04 +01:00
Danijel Simeunovic 4f4c26f58c finalizers 2026-03-06 15:25:51 +01:00
Danijel Simeunovic 02b879b576 cleanup 2026-03-06 09:54:07 +01:00
Danijel Simeunovic 2fac718488 readme 2026-03-06 09:51:20 +01:00
Danijel Simeunovic 2a1bc88ce5 details 2026-03-06 09:44:45 +01:00
Danijel Simeunovic e1ee176277 details 2026-03-06 09:44:29 +01:00
Danijel Simeunovic fdd013c1c8 apps group 2026-03-06 09:41:05 +01:00
Danijel Simeunovic 0955767ae6 shuffle 2026-03-06 09:32:36 +01:00
Danijel Simeunovic 33dd0a06c7 perm2 2026-03-06 09:30:04 +01:00
Danijel Simeunovic b23a23d385 permissions 2026-03-06 09:18:12 +01:00
Danijel Simeunovic cc69346de9 permissions 2026-03-06 09:15:19 +01:00
Danijel Simeunovic 671ae6e702 fix 2026-03-06 09:10:07 +01:00
Danijel Simeunovic 1f772ef7f5 policies 2026-03-06 08:58:04 +01:00
Danijel Simeunovic 2da39f7c67 default blocker 2026-03-06 08:50:51 +01:00
Danijel Simeunovic 1a621c038f secret cloner 2026-03-06 08:48:33 +01:00
Danijel Simeunovic daec2b1bb2 refine 2026-03-05 21:59:51 +01:00
Danijel Simeunovic 72d88ca533 trivy_vulnerability_id 2026-03-05 21:53:07 +01:00
Danijel Simeunovic eb084ef5cf new key 2026-03-05 19:08:34 +01:00
Danijel Simeunovic cfb456e705 cve 2026-03-05 16:04:55 +01:00
Danijel Simeunovic b4e735bfb8 exclude ns 2026-03-05 15:59:16 +01:00
Danijel Simeunovic 5e8712dbfb trivy reinstall 2026-03-05 15:51:11 +01:00
Danijel Simeunovic 893aab6877 uninstall trivy 2026-03-05 15:34:03 +01:00
Danijel Simeunovic a9b1287db0 new api key 2026-03-05 14:29:23 +01:00
Danijel Simeunovic bfc527bee4 dot-ai secret 2026-03-05 14:17:31 +01:00
Danijel Simeunovic df1359fe82 trivy config 2026-03-05 13:48:32 +01:00
Danijel Simeunovic 020b046859 trivy unfixed 2026-03-05 13:36:07 +01:00
Danijel Simeunovic 59eff39149 cve 2026-03-05 13:18:39 +01:00
Danijel Simeunovic ecf8bd6f12 trivy prom 2026-03-05 12:36:44 +01:00
Danijel Simeunovic 35e9b0c739 diffs 2026-03-05 11:43:13 +01:00
Danijel Simeunovic 24f9c956bc readme 2026-03-05 10:53:43 +01:00
Danijel Simeunovic d86eead4c4 remove stale lines 2026-03-05 10:45:08 +01:00
Danijel Simeunovic 1af57ea5c1 bootstrap main app 2026-03-05 10:28:15 +01:00
Danijel Simeunovic 04d39c71aa keys 2026-03-04 14:10:08 +01:00
Danijel Simeunovic 3ec8f48996 mcp- 2026-03-04 13:58:18 +01:00
Danijel Simeunovic b7fb5e2064 keys 2026-03-04 13:57:40 +01:00
Danijel Simeunovic dc246a46e8 mcp 2026-03-04 13:45:36 +01:00
Danijel Simeunovic 89753324dc finalizers 2026-03-04 13:36:23 +01:00
Danijel Simeunovic c4746047cd del 2026-03-04 13:29:17 +01:00
Danijel Simeunovic 449bf19c10 ns 2026-03-04 13:26:48 +01:00
Danijel Simeunovic 85650148f6 dot 2026-03-04 12:58:57 +01:00
Danijel Simeunovic 5f09242ebd clean 2026-03-04 12:28:22 +01:00
Danijel Simeunovic b1f01a6ff8 mcpcoder 2026-03-04 12:25:50 +01:00
Danijel Simeunovic cc2ed9e91a annot 2026-03-04 12:16:29 +01:00
Danijel Simeunovic 4a623fd3b4 payload 2026-03-04 12:05:10 +01:00
Danijel Simeunovic ef78bfa9c5 argo notifications 2026-03-04 11:13:41 +01:00
Danijel Simeunovic 5f99aed97a triggers 2026-03-04 11:10:44 +01:00
Danijel Simeunovic bea55380d5 kyverno diffs 2026-03-04 10:56:31 +01:00
Danijel Simeunovic ab58c8a62a argocd notifications 2026-03-04 10:50:35 +01:00
Danijel Simeunovic 4d3269a24c dot 2026-03-04 09:51:39 +01:00
Danijel Simeunovic e7b80014c3 del 2026-03-03 20:16:33 +01:00
Danijel Simeunovic 5d3f21170e audit 2026-03-03 20:05:42 +01:00
Danijel Simeunovic 86a7994bf8 mcpcoder 2026-03-03 19:13:04 +01:00
Danijel Simeunovic fa8b6dc0de dot-ai 2026-03-03 17:39:44 +01:00
Danijel Simeunovic edd468e239 sync 2026-02-27 14:12:09 +01:00
Danijel Simeunovic e7168373aa app of apps 2026-02-27 13:49:51 +01:00
Danijel Simeunovic 11fb039e23 comment 2026-02-27 13:43:00 +01:00
Danijel Simeunovic b2642c051f loki mem 2026-02-26 14:36:42 +01:00
Danijel Simeunovic c7b0dbe4a7 show pod in logs 2026-02-25 10:11:23 +01:00
Danijel Simeunovic 355fabcfd6 more refining 2026-02-25 10:04:26 +01:00
Danijel Simeunovic b17c790946 grafana refining 2026-02-25 09:51:32 +01:00
Danijel Simeunovic 6bb3440d8b loki fix 2026-02-25 09:43:27 +01:00
Danijel Simeunovic 748c491d4e fluentbit fixes 2026-02-25 09:34:27 +01:00
Danijel Simeunovic 143f115236 log fixes 2026-02-25 09:24:54 +01:00
Danijel Simeunovic bd1f347164 audit policy 2026-02-24 10:58:42 +01:00
Copilot 940cd66a6a Update README repository structure with missing files (#1)
* Initial plan

* Update README.md repository structure to reflect actual files

Co-authored-by: snothub <12095691+snothub@users.noreply.github.com>

* Fix spelling and tree structure formatting in README.md

Co-authored-by: snothub <12095691+snothub@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: snothub <12095691+snothub@users.noreply.github.com>
2026-02-19 15:46:26 +01:00
Danijel Simeunovic 4987b582f4 promfix 2026-02-18 15:11:24 +01:00
Danijel Simeunovic 480be6fef4 indent 2026-02-18 15:02:08 +01:00
Danijel Simeunovic f5c1dc0cbc enforce 2026-02-18 13:17:39 +01:00
Danijel Simeunovic a962fd6450 rule 2026-02-18 13:13:52 +01:00
Danijel Simeunovic e06a080eb5 bck 2026-02-18 12:38:20 +01:00
Danijel Simeunovic 259b43f642 skip 2026-02-18 12:35:02 +01:00
Danijel Simeunovic 74eadaeffb tune 2026-02-18 12:31:32 +01:00
Danijel Simeunovic f676379176 enforce 2026-02-18 12:27:33 +01:00
Danijel Simeunovic 5858a8b7d1 pre 2026-02-18 12:22:52 +01:00
Danijel Simeunovic 67a47528cc fix2 2026-02-18 12:19:19 +01:00
Danijel Simeunovic 788a3392bf fix 2026-02-18 12:15:51 +01:00
Danijel Simeunovic b27b5ad789 check 2026-02-18 12:11:44 +01:00
Danijel Simeunovic 79f48af2b5 depl checker 2026-02-18 12:06:11 +01:00
Danijel Simeunovic 08a2b3a807 enforce pod label 2026-02-18 10:44:13 +01:00
Danijel Simeunovic cb0766ffdd kyverno scraper 2026-02-17 15:26:02 +01:00
Danijel Simeunovic b7987caee8 diff 2026-02-17 14:45:04 +01:00
Danijel Simeunovic 691fd9926d name 2026-02-17 14:41:52 +01:00
Danijel Simeunovic 126eb7c01c kyvernopolicies 2026-02-17 14:40:59 +01:00
Danijel Simeunovic 706d15476c label checker audit 2026-02-17 14:38:00 +01:00
Danijel Simeunovic f32ae3d79f fbvals 2026-02-16 13:47:22 +01:00
Danijel Simeunovic 0f26bbda69 log fixes 2026-02-16 13:19:05 +01:00
Danijel Simeunovic 90fae4c415 simplify 2026-02-14 16:26:01 +01:00
Danijel Simeunovic 0db4f69bd7 loki fixes 2026-02-13 13:56:35 +01:00
Danijel Simeunovic 1ba2271ba2 fb config 2026-02-13 13:46:33 +01:00
Danijel Simeunovic 370261fe58 fluent bit config fix 2026-02-13 13:07:54 +01:00
Danijel Simeunovic 5437af59d3 kyverno diff 2026-02-13 12:30:39 +01:00
Danijel Simeunovic 7568280464 loki fix 2026-02-13 10:39:27 +01:00
Danijel Simeunovic 88d9f94736 Traefik health check fix 2026-02-13 10:00:37 +01:00
Danijel Simeunovic bad5ce2304 readme 2026-02-12 20:52:57 +01:00
Danijel Simeunovic bd754a016c app 2026-02-12 20:50:59 +01:00
Danijel Simeunovic c806decbb7 musicman 2026-02-12 15:37:27 +01:00
Danijel Simeunovic 26d4c3e236 values 2026-02-12 13:54:13 +01:00
Danijel Simeunovic 561ab00e66 helm values 2026-02-12 13:49:39 +01:00
Danijel Simeunovic af1591073a metrics 2026-02-12 11:38:42 +01:00
Danijel Simeunovic c8dfdeb170 no port 2026-02-12 11:31:19 +01:00
Danijel Simeunovic 8502395521 tr yaml 2026-02-12 11:29:21 +01:00
Danijel Simeunovic c73d24aff6 clean 2026-02-12 11:21:35 +01:00
Danijel Simeunovic 6fb0652efc add params 2026-02-12 11:17:28 +01:00
Danijel Simeunovic 23de8285ec traefik trustedIps 2026-02-11 23:21:35 +01:00
Danijel Simeunovic 4ec763ac65 traefik dash 2026-02-11 18:04:05 +01:00
Danijel Simeunovic 7b694ef16c traefik svc 2026-02-11 17:37:58 +01:00
Danijel Simeunovic 133619818b readme 2026-02-11 13:54:50 +01:00
Danijel Simeunovic 058a3cba67 trivy 2026-02-11 13:42:06 +01:00
Danijel Simeunovic 3f981e156e readme 2026-02-11 13:03:30 +01:00
Danijel Simeunovic d431bf9dac traefik annotations 2026-02-11 09:00:02 +01:00
Danijel Simeunovic 5ec64dede6 readme 2026-02-10 15:24:01 +01:00
Danijel Simeunovic 04a34b77d2 readme 2026-02-10 15:09:25 +01:00
Danijel Simeunovic 300651ce8f trivy ver 2026-02-10 10:31:46 +01:00
Danijel Simeunovic 94dc33e936 labels 2026-02-10 10:03:24 +01:00
Danijel Simeunovic c57a3f0db2 loki dns 2026-02-10 09:59:46 +01:00
Danijel Simeunovic dcfd9c8901 loki config 2026-02-09 21:48:53 +01:00
Danijel Simeunovic 98da1fd070 fluent bit loki config 2026-02-09 21:44:12 +01:00
Danijel Simeunovic 66bb754979 feedback app 2026-02-09 15:58:24 +01:00
Danijel Simeunovic 089f1cae17 grafana host 2026-02-09 15:50:01 +01:00
Danijel Simeunovic 0170c5e4bf remove policy 2026-02-09 15:38:40 +01:00
Danijel Simeunovic d35bbeadab remove presync hook 2026-02-09 15:18:15 +01:00
Danijel Simeunovic 10c0d19bfb fixes 2026-02-09 15:14:00 +01:00
Danijel Simeunovic e4434a40bd target rev 2026-02-09 15:06:41 +01:00
Danijel Simeunovic 0f229a9360 kyverno ver 2026-02-09 15:04:51 +01:00
Danijel Simeunovic fa0b715efb sh 2026-02-09 15:02:55 +01:00
Danijel Simeunovic 1813777146 cleanup 2026-02-09 09:42:50 +01:00
Danijel Simeunovic e62eb606cd readme 2026-02-09 09:05:00 +01:00
Danijel Simeunovic 52bc288081 values 2026-02-08 23:54:32 +01:00
Danijel Simeunovic e2cb0e85f1 labels 2026-02-08 23:52:08 +01:00
Danijel Simeunovic 3e9528dd6f folderstructure 2026-02-08 23:48:12 +01:00
Danijel Simeunovic 0bd3f0cb63 folder 2026-02-08 23:46:33 +01:00
Danijel Simeunovic 193a402906 labels 2026-02-08 23:45:30 +01:00
Danijel Simeunovic 2c4e75aafb folders 2026-02-08 23:42:42 +01:00
Danijel Simeunovic 19b61d8308 move 2026-02-08 23:34:57 +01:00
Danijel Simeunovic 94843f86f9 hook delete 2026-02-08 23:32:51 +01:00
Danijel Simeunovic cc8d815d5a infra 2026-02-08 23:30:36 +01:00
Danijel Simeunovic da984196f5 move 2026-02-08 23:28:44 +01:00
Danijel Simeunovic 5c2038353f backup 2026-02-08 23:27:16 +01:00
Danijel Simeunovic 81ef533fc6 wave 2026-02-08 23:16:38 +01:00
Danijel Simeunovic db6478b794 simplify 2026-02-08 23:15:47 +01:00
Danijel Simeunovic eed88fe233 test apps 2026-02-08 23:03:16 +01:00
Danijel Simeunovic fe3794f691 simplify 2026-02-08 17:07:45 +01:00
Danijel Simeunovic b54ed92f7c path 2026-02-08 17:06:53 +01:00
Danijel Simeunovic ba90ecac6d repo 2026-02-08 17:00:29 +01:00
Danijel Simeunovic fa4e7c1984 simplify 2026-02-08 16:59:38 +01:00
Danijel Simeunovic 65171ddcb7 spacing 2026-02-08 16:52:13 +01:00
Danijel Simeunovic 9eb7cdae1b kust2 2026-02-08 16:51:20 +01:00
Danijel Simeunovic 4ffd7b91a0 kust 2026-02-08 16:50:42 +01:00
Danijel Simeunovic 2b9365438a path 2026-02-08 16:06:22 +01:00
Danijel Simeunovic b9392f572c repo 2026-02-08 16:04:49 +01:00
Danijel Simeunovic 0abb69d017 kustomize 2 2026-02-08 16:03:47 +01:00
Danijel Simeunovic fd248d5e89 timeout 2026-02-08 16:02:30 +01:00
Danijel Simeunovic 7f2b904669 kustomize 2026-02-08 16:01:53 +01:00
Danijel Simeunovic 3e943b5405 repo fix 2026-02-08 13:39:31 +01:00
Danijel Simeunovic f9f5806419 timeout 2026-02-08 13:37:33 +01:00
Danijel Simeunovic 08931e4677 app set 2026-02-08 13:36:48 +01:00
Danijel Simeunovic 774852bd1b recurse 2026-02-08 13:32:10 +01:00
Danijel Simeunovic ddcb324bf0 timeout 2026-02-08 13:27:40 +01:00
Danijel Simeunovic 0992b7b1c9 argocd hooks and phases 2026-02-08 12:39:54 +01:00
Danijel Simeunovic f690a1264d remove istio ref 2026-02-08 10:45:04 +01:00
Danijel Simeunovic b371ebb453 edit 2026-02-08 10:42:39 +01:00
Danijel Simeunovic bec3b6310a reorg 2026-02-08 10:42:10 +01:00
Danijel Simeunovic a42e94672e md 2026-02-07 21:41:35 +01:00
Danijel Simeunovic bbc863995d argo report 2026-02-07 21:40:13 +01:00
Danijel Simeunovic 1722807d7c readme 2026-02-07 21:39:16 +01:00
Danijel Simeunovic d487dffef0 delete app 2026-02-07 21:38:20 +01:00
Danijel Simeunovic 45d03792c6 minor adjustments 2026-02-07 21:35:40 +01:00
Danijel Simeunovic bc52bfd3f1 adjustments to argo apps 2026-02-07 21:31:56 +01:00
Danijel Simeunovic 2b591593c0 fluentbit values 2026-02-07 21:28:19 +01:00
Danijel Simeunovic d57387b1e8 readme 2026-02-07 21:21:36 +01:00
Danijel Simeunovic 3e3c806949 readme.md 2026-02-07 21:21:08 +01:00
Danijel Simeunovic a42a07b669 initial 2026-02-07 21:16:15 +01:00
357 changed files with 21284 additions and 25160 deletions
+2
View File
@@ -0,0 +1,2 @@
# Force LF line endings for shell scripts
*.sh text eol=lf
+47
View File
@@ -0,0 +1,47 @@
name: AI Code Review
on:
pull_request:
types: [ labeled, synchronize ]
jobs:
ai-review:
if: >-
(github.event.action == 'synchronized' && contains(toJSON(github.event.pull_request.labels), 'ai-review')) || contains(toJSON(gitea.event.changes.added_labels), 'ai-review')
runs-on: ubuntu-latest
env:
AI_REVIEW_CONFIG_FILE_YAML: ./shared-prompts/iac/.ai-review.yaml
# VCS configuration
VCS__PROVIDER: GITEA
VCS__PIPELINE__OWNER: ${{ github.repository_owner }}
VCS__PIPELINE__REPO: ${{ github.event.repository.name }}
VCS__PIPELINE__PULL_NUMBER: ${{ github.event.pull_request.number }}
VCS__HTTP_CLIENT__API_URL: https://git.forteapps.net/api/v1
VCS__HTTP_CLIENT__API_TOKEN: ${{ secrets.AI_REVIEW_TOKEN }}
# Review — disable fallback to see real Gitea API errors
REVIEW__INLINE_COMMENT_FALLBACK: "false"
# LLM configuration
LLM__PROVIDER: CLAUDE
LLM__META__MODEL: claude-sonnet-4-20250514
LLM__META__MAX_TOKENS: "4096"
LLM__HTTP_CLIENT__API_URL: https://api.anthropic.com
LLM__HTTP_CLIENT__API_TOKEN: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
token: ${{ secrets.AI_REVIEW_TOKEN }}
- name: Run inline review
uses: docker://nikitafilonov/ai-review:v0.64.0
with:
args: ai-review run-inline
- name: Run summary review
uses: docker://nikitafilonov/ai-review:v0.64.0
with:
args: ai-review run-summary
+20
View File
@@ -0,0 +1,20 @@
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install TruffleHog
run: |
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh \
| sh -s -- -b /usr/local/bin
- name: Secret Scanning
run: trufflehog git file://. --fail --no-update --results=verified,unknown
+26
View File
@@ -0,0 +1,26 @@
# User-specific files
*.user
*.lock
*.userosscache
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
private/
.helm/
temp/
*.orig
CLAUDE.md
.claude/
devbox.d/
devbox.lock
.devbox/
bash.exe.stackdump
# OpenTofu
.tofu/configs/*.env
.tofu/scripts/*.config
.tofu/platforms/**/.terraform/
.tofu/platforms/**/terraform.tfstate*
.tofu/platforms/**/tfplan
.tofu/platforms/**/.terraform.lock.hcl
+3
View File
@@ -0,0 +1,3 @@
[submodule "shared-prompts"]
path = shared-prompts
url = https://git.forteapps.net/Forte/ai-review-prompts.git
+9
View File
@@ -0,0 +1,9 @@
# Azure AKS credentials — copy to aks.env and fill in values
# NEVER commit aks.env to git!
# Required
AZURE_TENANT_ID=your-azure-tenant-id
AZURE_SUBSCRIPTION_ID=your-azure-subscription-id
# Optional — defaults to cluster name if not set
ARM_RESOURCE_GROUP=
+10
View File
@@ -0,0 +1,10 @@
# AWS EKS credentials — copy to eks.env and fill in values
# NEVER commit eks.env to git!
# Required — AWS CLI profile or access key
AWS_PROFILE=default
AWS_REGION=eu-west-1
# Optional — override with explicit keys instead of profile
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
+9
View File
@@ -0,0 +1,9 @@
# GCP GKE credentials — copy to gke.env and fill in values
# NEVER commit gke.env to git!
# Required
GCP_PROJECT_ID=your-gcp-project-id
GCP_REGION=europe-west4
# Optional — path to service account JSON key (if not using gcloud auth)
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json
+8
View File
@@ -0,0 +1,8 @@
# UpCloud credentials — copy to upc.env and fill in values
# NEVER commit upc.env to git!
# Required
UPCLOUD_TOKEN=your-upcloud-api-token
# Optional — set after cluster creation for kubeconfig retrieval
UPCLOUD_CLUSTER_ID=
+18
View File
@@ -0,0 +1,18 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst-dev"
location = "norwayeast"
resource_group_name = "clst-dev-rg"
# AKS — small dev nodes
aks_node_vm_size = "Standard_B2s"
aks_node_count = 2
enable_delete_lock = false
tags = {
Environment = "dev"
ManagedBy = "tofu"
}
}
+26
View File
@@ -0,0 +1,26 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
value = module.cluster.cluster_name
}
output "resource_group_name" {
value = module.cluster.resource_group_name
}
output "kubernetes_version" {
value = module.cluster.kubernetes_version
}
output "location" {
value = module.cluster.location
}
output "oidc_issuer_url" {
value = module.cluster.oidc_issuer_url
}
output "kubeconfig" {
value = module.cluster.kubeconfig
sensitive = true
}
+17
View File
@@ -0,0 +1,17 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
# Credentials via environment variables:
# ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET
# Or: az login (uses your Azure CLI session)
}
@@ -0,0 +1,72 @@
# Current Azure/Entra ID context — provides tenant_id used in outputs
data "azurerm_client_config" "current" {}
# ─── Resource Group ───────────────────────────────────────────────────
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
resource "azurerm_management_lock" "main" {
count = var.enable_delete_lock ? 1 : 0
name = "${var.prefix}-delete-lock"
scope = azurerm_resource_group.main.id
lock_level = "CanNotDelete"
notes = "Prevents accidental deletion of production resources"
}
# ─── Networking ───────────────────────────────────────────────────────
resource "azurerm_virtual_network" "main" {
name = "${var.prefix}-vnet"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = [var.vnet_address_space]
tags = var.tags
}
# AKS nodes subnet
resource "azurerm_subnet" "aks" {
name = "${var.prefix}-aks-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.aks_subnet_cidr]
}
# ─── AKS Cluster ──────────────────────────────────────────────────────
resource "azurerm_kubernetes_cluster" "main" {
name = "${var.prefix}-aks"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
dns_prefix = replace(var.prefix, "-", "")
kubernetes_version = var.aks_kubernetes_version
tags = var.tags
default_node_pool {
name = "system"
node_count = var.aks_node_count
vm_size = var.aks_node_vm_size
vnet_subnet_id = azurerm_subnet.aks.id
node_labels = {
prefix = var.prefix
role = "worker"
env = lookup(var.tags, "Environment", "dev")
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "azure"
}
# Enable Workload Identity for keyless Azure service access (MSI)
oidc_issuer_enabled = true
workload_identity_enabled = true
}
@@ -0,0 +1,32 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
description = "AKS cluster name"
value = azurerm_kubernetes_cluster.main.name
}
output "resource_group_name" {
description = "Resource group name"
value = azurerm_resource_group.main.name
}
output "kubernetes_version" {
description = "Kubernetes version"
value = azurerm_kubernetes_cluster.main.kubernetes_version
}
output "location" {
description = "Azure region"
value = azurerm_resource_group.main.location
}
output "oidc_issuer_url" {
description = "AKS OIDC issuer URL (for workload identity federation)"
value = azurerm_kubernetes_cluster.main.oidc_issuer_url
}
output "kubeconfig" {
description = "Kubeconfig for the AKS cluster"
value = azurerm_kubernetes_cluster.main.kube_config_raw
sensitive = true
}
@@ -0,0 +1,18 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 3.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
@@ -0,0 +1,56 @@
# ─── Cluster ─────────────────────────────────────────────────────────
variable "prefix" {
description = "Prefix for resource names"
type = string
}
variable "location" {
description = "Azure region (e.g., norwayeast, westeurope, northeurope)"
type = string
}
variable "resource_group_name" {
description = "Name of the Azure Resource Group to create"
type = string
}
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = string
default = "10.100.0.0/16"
}
variable "aks_subnet_cidr" {
description = "CIDR block for the AKS node subnet"
type = string
default = "10.100.0.0/22"
}
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"
type = string
}
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
}
variable "aks_kubernetes_version" {
description = "Kubernetes version for AKS (null = latest stable)"
type = string
default = null
}
variable "enable_delete_lock" {
description = "Protect the resource group from accidental deletion"
type = bool
default = false
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {}
}
+18
View File
@@ -0,0 +1,18 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst"
location = "westeurope"
resource_group_name = "clst-prod-rg"
# AKS — general-purpose nodes for production
aks_node_vm_size = "Standard_D4s_v3"
aks_node_count = 3
enable_delete_lock = true
tags = {
Environment = "prod"
ManagedBy = "tofu"
}
}
+26
View File
@@ -0,0 +1,26 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
value = module.cluster.cluster_name
}
output "resource_group_name" {
value = module.cluster.resource_group_name
}
output "kubernetes_version" {
value = module.cluster.kubernetes_version
}
output "location" {
value = module.cluster.location
}
output "oidc_issuer_url" {
value = module.cluster.oidc_issuer_url
}
output "kubeconfig" {
value = module.cluster.kubeconfig
sensitive = true
}
+17
View File
@@ -0,0 +1,17 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
# Credentials via environment variables:
# ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET
# Or: az login (uses your Azure CLI session)
}
+173
View File
@@ -0,0 +1,173 @@
# =============================================================================
# Azure Workload Cluster
# =============================================================================
# A lean AKS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env azure-workload
# ./deploy-workload.sh --env azure-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names (e.g., clst-workload)"
type = string
default = "clst-workload"
}
variable "location" {
description = "Azure region"
type = string
default = "norwayeast"
}
variable "resource_group_name" {
description = "Name of the Azure Resource Group to create"
type = string
default = "clst-workload-rg"
}
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = string
default = "10.110.0.0/16"
}
variable "aks_subnet_cidr" {
description = "CIDR block for the AKS node subnet"
type = string
default = "10.110.0.0/22"
}
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes"
type = string
default = "Standard_B2s"
}
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
default = 2
}
variable "aks_kubernetes_version" {
description = "Kubernetes version for AKS (null = latest stable)"
type = string
default = null
}
variable "domain" {
description = "Public domain name — must have an existing Azure DNS zone"
type = string
}
variable "dns_zone_resource_group" {
description = "Resource group containing the Azure DNS zone (defaults to cluster RG)"
type = string
default = ""
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
# ─── Resource Group ───────────────────────────────────────────────────
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
# ─── Networking ───────────────────────────────────────────────────────
resource "azurerm_virtual_network" "main" {
name = "${var.prefix}-vnet"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = [var.vnet_address_space]
tags = var.tags
}
resource "azurerm_subnet" "aks" {
name = "${var.prefix}-aks-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.aks_subnet_cidr]
}
# ─── AKS Cluster ──────────────────────────────────────────────────────
resource "azurerm_kubernetes_cluster" "main" {
name = "${var.prefix}-aks"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
dns_prefix = replace(var.prefix, "-", "")
kubernetes_version = var.aks_kubernetes_version
tags = var.tags
default_node_pool {
name = "system"
node_count = var.aks_node_count
vm_size = var.aks_node_vm_size
vnet_subnet_id = azurerm_subnet.aks.id
node_labels = {
prefix = var.prefix
role = "worker"
env = lookup(var.tags, "Environment", "workload")
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "azure"
}
oidc_issuer_enabled = true
workload_identity_enabled = true
}
# ─── External-DNS Workload Identity ──────────────────────────────────
# Allows external-dns to manage Azure DNS records for app ingresses.
data "azurerm_dns_zone" "main" {
name = var.domain
resource_group_name = var.dns_zone_resource_group != "" ? var.dns_zone_resource_group : azurerm_resource_group.main.name
}
resource "azurerm_user_assigned_identity" "external_dns" {
name = "${var.prefix}-external-dns-identity"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
tags = var.tags
}
resource "azurerm_role_assignment" "external_dns_dns_contributor" {
scope = data.azurerm_dns_zone.main.id
role_definition_name = "DNS Zone Contributor"
principal_id = azurerm_user_assigned_identity.external_dns.principal_id
}
resource "azurerm_federated_identity_credential" "external_dns" {
name = "${var.prefix}-external-dns-fedcred"
resource_group_name = azurerm_resource_group.main.name
parent_id = azurerm_user_assigned_identity.external_dns.id
audience = ["api://AzureADTokenExchange"]
issuer = azurerm_kubernetes_cluster.main.oidc_issuer_url
subject = "system:serviceaccount:external-dns:external-dns"
}
+4
View File
@@ -0,0 +1,4 @@
output "cluster_name" { value = azurerm_kubernetes_cluster.main.name }
output "resource_group_name" { value = azurerm_resource_group.main.name }
output "location" { value = azurerm_resource_group.main.location }
output "external_dns_identity_client_id" { value = azurerm_user_assigned_identity.external_dns.client_id }
+21
View File
@@ -0,0 +1,21 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
# Credentials via environment variables:
# ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET
# Or: az login (uses your Azure CLI session)
}
+21
View File
@@ -0,0 +1,21 @@
module "cluster" {
source = "../modules/cluster"
region = var.region
prefix = "clst-dev"
# VPC
availability_zones = ["${var.region}a", "${var.region}b"]
# EKS — small dev nodes
node_instance_type = "t3.medium"
node_count = 2
node_min_count = 1
node_max_count = 4
kubernetes_version = "1.30"
tags = {
Environment = "dev"
ManagedBy = "tofu"
}
}
+5
View File
@@ -0,0 +1,5 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "aws_region" { value = module.cluster.aws_region }
output "oidc_issuer_url" { value = module.cluster.oidc_issuer_url }
output "oidc_provider_arn" { value = module.cluster.oidc_provider_arn }
output "vpc_id" { value = module.cluster.vpc_id }
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
# Authentication: set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
# or configure an AWS profile: export AWS_PROFILE=clst
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region for dev environment"
type = string
default = "eu-west-1"
}
+207
View File
@@ -0,0 +1,207 @@
# ─── VPC ──────────────────────────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, { Name = "${var.prefix}-vpc" })
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(var.tags, { Name = "${var.prefix}-igw" })
}
# Public subnets (one per AZ) — for NAT gateways and load balancers
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.prefix}-public-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/elb" = "1"
})
}
# Private subnets (one per AZ) — for EKS nodes
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.prefix}-private-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/internal-elb" = "1"
})
}
# NAT Gateway (single, in first public subnet — use one per AZ for prod HA)
resource "aws_eip" "nat" {
domain = "vpc"
tags = merge(var.tags, { Name = "${var.prefix}-nat-eip" })
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = merge(var.tags, { Name = "${var.prefix}-nat" })
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-public-rt" })
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-private-rt" })
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
# ─── EKS Cluster ──────────────────────────────────────────────────────
resource "aws_iam_role" "eks_cluster" {
name_prefix = "${var.prefix}-eks-cluster-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "eks.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.eks_cluster.name
}
resource "aws_eks_cluster" "main" {
name = "${var.prefix}-eks"
role_arn = aws_iam_role.eks_cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)
endpoint_private_access = true
endpoint_public_access = true
}
# Enable OIDC issuer for IRSA (IAM Roles for Service Accounts)
access_config {
authentication_mode = "API_AND_CONFIG_MAP"
}
tags = var.tags
depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}
# OIDC provider — required for IRSA (IAM Roles for Service Accounts)
data "tls_certificate" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
tags = var.tags
}
# EKS Node Group
resource "aws_iam_role" "eks_nodes" {
name_prefix = "${var.prefix}-eks-nodes-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_ecr_readonly" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
role = aws_iam_role.eks_nodes.name
}
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "${var.prefix}-nodes"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = aws_subnet.private[*].id
instance_types = [var.node_instance_type]
scaling_config {
desired_size = var.node_count
max_size = var.node_max_count
min_size = var.node_min_count
}
update_config {
max_unavailable = 1
}
tags = var.tags
depends_on = [
aws_iam_role_policy_attachment.eks_worker_node_policy,
aws_iam_role_policy_attachment.eks_cni_policy,
aws_iam_role_policy_attachment.eks_ecr_readonly,
]
}
@@ -0,0 +1,26 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
description = "EKS cluster name"
value = aws_eks_cluster.main.name
}
output "aws_region" {
description = "AWS region"
value = var.region
}
output "oidc_issuer_url" {
description = "EKS OIDC issuer URL (for IRSA)"
value = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
output "oidc_provider_arn" {
description = "IAM OIDC provider ARN (for IRSA trust policies)"
value = aws_iam_openid_connect_provider.eks.arn
}
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
@@ -0,0 +1,12 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
@@ -0,0 +1,61 @@
# ─── Region ──────────────────────────────────────────────────────────
variable "region" {
description = "AWS region (e.g., eu-west-1, us-east-1)"
type = string
}
variable "prefix" {
description = "Prefix for resource names (e.g., clst-dev)"
type = string
}
# ─── Networking ───────────────────────────────────────────────────────
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.100.0.0/16"
}
variable "availability_zones" {
description = "List of AZs for subnets (23 recommended)"
type = list(string)
}
# ─── EKS Cluster ─────────────────────────────────────────────────────
variable "node_instance_type" {
description = "EKS node instance type (e.g., t3.medium, m5.xlarge)"
type = string
}
variable "node_count" {
description = "Desired number of EKS worker nodes"
type = number
}
variable "node_min_count" {
description = "Minimum number of EKS worker nodes"
type = number
default = 1
}
variable "node_max_count" {
description = "Maximum number of EKS worker nodes"
type = number
}
variable "kubernetes_version" {
description = "Kubernetes version for EKS (e.g., \"1.30\")"
type = string
default = "1.30"
}
# ─── Tags ─────────────────────────────────────────────────────────────
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {}
}
+21
View File
@@ -0,0 +1,21 @@
module "cluster" {
source = "../modules/cluster"
region = var.region
prefix = "clst"
# VPC
availability_zones = ["${var.region}a", "${var.region}b", "${var.region}c"]
# EKS — general-purpose nodes for production
node_instance_type = "m5.xlarge"
node_count = 3
node_min_count = 3
node_max_count = 6
kubernetes_version = "1.30"
tags = {
Environment = "prod"
ManagedBy = "tofu"
}
}
+5
View File
@@ -0,0 +1,5 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "aws_region" { value = module.cluster.aws_region }
output "oidc_issuer_url" { value = module.cluster.oidc_issuer_url }
output "oidc_provider_arn" { value = module.cluster.oidc_provider_arn }
output "vpc_id" { value = module.cluster.vpc_id }
+22
View File
@@ -0,0 +1,22 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region for prod environment"
type = string
default = "eu-west-1"
}
+339
View File
@@ -0,0 +1,339 @@
# =============================================================================
# AWS Workload Cluster
# =============================================================================
# A lean EKS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env aws-workload
# ./deploy-workload.sh --env aws-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names (e.g., clst-workload)"
type = string
default = "clst-workload"
}
variable "availability_zones" {
description = "List of AZs for subnets"
type = list(string)
default = ["eu-west-1a", "eu-west-1b"]
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.110.0.0/16"
}
variable "node_instance_type" {
description = "EKS node instance type"
type = string
default = "t3.medium"
}
variable "node_count" {
description = "Desired number of EKS worker nodes"
type = number
default = 2
}
variable "node_min_count" {
description = "Minimum number of EKS worker nodes"
type = number
default = 1
}
variable "node_max_count" {
description = "Maximum number of EKS worker nodes"
type = number
default = 4
}
variable "kubernetes_version" {
description = "Kubernetes version for EKS"
type = string
default = "1.30"
}
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
# ─── VPC ──────────────────────────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, { Name = "${var.prefix}-vpc" })
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(var.tags, { Name = "${var.prefix}-igw" })
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.prefix}-public-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/elb" = "1"
})
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.prefix}-private-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/internal-elb" = "1"
})
}
resource "aws_eip" "nat" {
domain = "vpc"
tags = merge(var.tags, { Name = "${var.prefix}-nat-eip" })
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = merge(var.tags, { Name = "${var.prefix}-nat" })
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-public-rt" })
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-private-rt" })
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
# ─── EKS Cluster ──────────────────────────────────────────────────────
resource "aws_iam_role" "eks_cluster" {
name_prefix = "${var.prefix}-eks-cluster-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "eks.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.eks_cluster.name
}
resource "aws_eks_cluster" "main" {
name = "${var.prefix}-eks"
role_arn = aws_iam_role.eks_cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)
endpoint_private_access = true
endpoint_public_access = true
}
access_config {
authentication_mode = "API_AND_CONFIG_MAP"
}
tags = var.tags
depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}
# OIDC provider — required for IRSA
data "tls_certificate" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
tags = var.tags
}
resource "aws_iam_role" "eks_nodes" {
name_prefix = "${var.prefix}-eks-nodes-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_ecr_readonly" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
role = aws_iam_role.eks_nodes.name
}
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "${var.prefix}-nodes"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = aws_subnet.private[*].id
instance_types = [var.node_instance_type]
scaling_config {
desired_size = var.node_count
max_size = var.node_max_count
min_size = var.node_min_count
}
update_config {
max_unavailable = 1
}
tags = var.tags
depends_on = [
aws_iam_role_policy_attachment.eks_worker_node_policy,
aws_iam_role_policy_attachment.eks_cni_policy,
aws_iam_role_policy_attachment.eks_ecr_readonly,
]
}
# ─── External-DNS IRSA ───────────────────────────────────────────────
# Allows external-dns to manage Route53 records for app ingresses.
data "aws_route53_zone" "main" {
name = var.domain
private_zone = false
}
data "aws_iam_policy_document" "external_dns_assume_role" {
statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.eks.arn]
}
actions = ["sts:AssumeRoleWithWebIdentity"]
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub"
values = ["system:serviceaccount:external-dns:external-dns"]
}
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:aud"
values = ["sts.amazonaws.com"]
}
}
}
resource "aws_iam_role" "external_dns_irsa" {
name_prefix = "${var.prefix}-external-dns-irsa-"
assume_role_policy = data.aws_iam_policy_document.external_dns_assume_role.json
tags = var.tags
}
data "aws_iam_policy_document" "external_dns_route53" {
statement {
effect = "Allow"
actions = ["route53:ChangeResourceRecordSets"]
resources = ["arn:aws:route53:::hostedzone/${data.aws_route53_zone.main.zone_id}"]
}
statement {
effect = "Allow"
actions = ["route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource"]
resources = ["*"]
}
}
resource "aws_iam_role_policy" "external_dns_route53" {
name_prefix = "${var.prefix}-external-dns-route53-"
role = aws_iam_role.external_dns_irsa.id
policy = data.aws_iam_policy_document.external_dns_route53.json
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = aws_eks_cluster.main.name }
output "aws_region" { value = var.region }
output "external_dns_irsa_role_arn" { value = aws_iam_role.external_dns_irsa.arn }
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
# Authentication: set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
# or configure an AWS profile: export AWS_PROFILE=clst
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region for the workload environment"
type = string
default = "eu-west-1"
}
+17
View File
@@ -0,0 +1,17 @@
module "cluster" {
source = "../modules/cluster"
project_id = var.project_id
region = var.region
prefix = "clst-dev"
# GKE — small dev nodes
node_machine_type = "e2-standard-2"
node_count = 2
deletion_protection = false
labels = {
environment = "dev"
managed-by = "tofu"
}
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "project_id" { value = module.cluster.project_id }
output "region" { value = module.cluster.region }
+26
View File
@@ -0,0 +1,26 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
# Authentication: use Application Default Credentials (gcloud auth application-default login)
# or set GOOGLE_APPLICATION_CREDENTIALS to a service account key file.
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID for the dev environment"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west4"
}
+115
View File
@@ -0,0 +1,115 @@
# ─── Required APIs ────────────────────────────────────────────────────
resource "google_project_service" "compute" {
project = var.project_id
service = "compute.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "container" {
project = var.project_id
service = "container.googleapis.com"
disable_on_destroy = false
}
# ─── Networking ───────────────────────────────────────────────────────
resource "google_compute_network" "main" {
project = var.project_id
name = "${var.prefix}-vpc"
auto_create_subnetworks = false
depends_on = [google_project_service.compute]
}
resource "google_compute_subnetwork" "main" {
project = var.project_id
name = "${var.prefix}-subnet"
ip_cidr_range = "10.100.0.0/22"
region = var.region
network = google_compute_network.main.id
# Secondary ranges required for GKE VPC-native cluster
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.200.0.0/14" # /14 = ~262k pod IPs
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.204.0.0/20" # /20 = ~4k service IPs
}
}
# ─── GKE Cluster ──────────────────────────────────────────────────────
#
# Regional cluster (3 control-plane replicas) for HA.
# Workload Identity enabled — allows K8s service accounts to impersonate
# Google Service Accounts for keyless access to GCP services.
resource "google_container_cluster" "main" {
project = var.project_id
name = "${var.prefix}-gke"
location = var.region # regional cluster
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.main.id
# VPC-native cluster with alias IP ranges
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
# Workload Identity pool — enables OIDC token projection for pods
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
# Remove default node pool — we manage our own below
remove_default_node_pool = true
initial_node_count = 1
deletion_protection = var.deletion_protection
dynamic "release_channel" {
for_each = var.kubernetes_version == null ? [1] : []
content {
channel = "STABLE"
}
}
resource_labels = var.labels
depends_on = [google_project_service.container]
}
resource "google_container_node_pool" "main" {
project = var.project_id
name = "${var.prefix}-nodes"
location = var.region
cluster = google_container_cluster.main.name
node_count = var.node_count
node_config {
machine_type = var.node_machine_type
# GKE_METADATA mode is required for Workload Identity
workload_metadata_config {
mode = "GKE_METADATA"
}
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform",
]
labels = merge(var.labels, {
role = "worker"
})
}
management {
auto_repair = true
auto_upgrade = true
}
}
@@ -0,0 +1,16 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
description = "GKE cluster name"
value = google_container_cluster.main.name
}
output "project_id" {
description = "GCP project ID"
value = var.project_id
}
output "region" {
description = "GCP region"
value = var.region
}
@@ -0,0 +1,8 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
@@ -0,0 +1,48 @@
# ─── Project / Region ────────────────────────────────────────────────
variable "project_id" {
description = "GCP project ID"
type = string
}
variable "region" {
description = "GCP region (e.g., europe-west4, europe-west1)"
type = string
}
variable "prefix" {
description = "Prefix for resource names (e.g., clst-dev)"
type = string
}
# ─── GKE Cluster ─────────────────────────────────────────────────────
variable "node_machine_type" {
description = "GKE node machine type (e.g., e2-standard-2, e2-standard-4)"
type = string
}
variable "node_count" {
description = "Number of nodes per zone (regional cluster spawns nodes in each zone)"
type = number
}
variable "kubernetes_version" {
description = "GKE Kubernetes version channel (null = STABLE release channel)"
type = string
default = null
}
variable "deletion_protection" {
description = "Prevent cluster deletion (set true for production)"
type = bool
default = false
}
# ─── Labels ──────────────────────────────────────────────────────────
variable "labels" {
description = "Labels applied to all resources"
type = map(string)
default = {}
}
+17
View File
@@ -0,0 +1,17 @@
module "cluster" {
source = "../modules/cluster"
project_id = var.project_id
region = var.region
prefix = "clst"
# GKE — general-purpose nodes for production
node_machine_type = "e2-standard-4"
node_count = 3
deletion_protection = true
labels = {
environment = "prod"
managed-by = "tofu"
}
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "project_id" { value = module.cluster.project_id }
output "region" { value = module.cluster.region }
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID for the prod environment"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west1"
}
+194
View File
@@ -0,0 +1,194 @@
# =============================================================================
# GCP Workload Cluster
# =============================================================================
# A lean GKE cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env gcp-workload
# ./deploy-workload.sh --env gcp-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names (e.g., clst-workload)"
type = string
default = "clst-workload"
}
variable "node_machine_type" {
description = "GKE node machine type"
type = string
default = "e2-standard-2"
}
variable "node_count" {
description = "Number of nodes per zone"
type = number
default = 1
}
variable "kubernetes_version" {
description = "GKE Kubernetes version (null = STABLE release channel)"
type = string
default = null
}
variable "deletion_protection" {
description = "Prevent cluster deletion"
type = bool
default = false
}
variable "labels" {
description = "Labels applied to all resources"
type = map(string)
default = {
environment = "workload"
managed-by = "tofu"
}
}
# ─── Required APIs ────────────────────────────────────────────────────
resource "google_project_service" "compute" {
project = var.project_id
service = "compute.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "container" {
project = var.project_id
service = "container.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "iam" {
project = var.project_id
service = "iam.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "dns" {
project = var.project_id
service = "dns.googleapis.com"
disable_on_destroy = false
}
# ─── Networking ───────────────────────────────────────────────────────
resource "google_compute_network" "main" {
project = var.project_id
name = "${var.prefix}-vpc"
auto_create_subnetworks = false
depends_on = [google_project_service.compute]
}
resource "google_compute_subnetwork" "main" {
project = var.project_id
name = "${var.prefix}-subnet"
ip_cidr_range = "10.110.0.0/22"
region = var.region
network = google_compute_network.main.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.210.0.0/14"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.214.0.0/20"
}
}
# ─── GKE Cluster ──────────────────────────────────────────────────────
resource "google_container_cluster" "main" {
project = var.project_id
name = "${var.prefix}-gke"
location = var.region
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.main.id
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
remove_default_node_pool = true
initial_node_count = 1
deletion_protection = var.deletion_protection
dynamic "release_channel" {
for_each = var.kubernetes_version == null ? [1] : []
content {
channel = "STABLE"
}
}
resource_labels = var.labels
depends_on = [google_project_service.container]
}
resource "google_container_node_pool" "main" {
project = var.project_id
name = "${var.prefix}-nodes"
location = var.region
cluster = google_container_cluster.main.name
node_count = var.node_count
node_config {
machine_type = var.node_machine_type
workload_metadata_config {
mode = "GKE_METADATA"
}
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform",
]
labels = merge(var.labels, { role = "worker" })
}
management {
auto_repair = true
auto_upgrade = true
}
}
# ─── External-DNS Workload Identity ──────────────────────────────────
# Allows external-dns to manage Cloud DNS records for app ingresses.
resource "google_service_account" "external_dns" {
project = var.project_id
account_id = "${var.prefix}-external-dns"
display_name = "External-DNS Service Account (Workload Identity)"
depends_on = [google_project_service.iam]
}
resource "google_project_iam_member" "external_dns_dns_admin" {
project = var.project_id
role = "roles/dns.admin"
member = "serviceAccount:${google_service_account.external_dns.email}"
}
resource "google_service_account_iam_member" "external_dns_workload_identity" {
service_account_id = google_service_account.external_dns.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[external-dns/external-dns]"
}
+4
View File
@@ -0,0 +1,4 @@
output "cluster_name" { value = google_container_cluster.main.name }
output "project_id" { value = var.project_id }
output "region" { value = var.region }
output "external_dns_gsa_email" { value = google_service_account.external_dns.email }
+26
View File
@@ -0,0 +1,26 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
# Authentication: use Application Default Credentials (gcloud auth application-default login)
# or set GOOGLE_APPLICATION_CREDENTIALS to a service account key file.
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID for the workload environment"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west4"
}
+14
View File
@@ -0,0 +1,14 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst-dev"
zone = "no-svg1"
node_plan = "DEV-1xCPU-2GB"
node_count = 2
network_cidr = "10.100.0.0/24"
tags = {
Environment = "dev"
ManagedBy = "tofu"
}
}
+13
View File
@@ -0,0 +1,13 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_id" {
value = module.cluster.cluster_id
}
output "cluster_name" {
value = module.cluster.cluster_name
}
output "zone" {
value = module.cluster.zone
}
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
provider "upcloud" {
# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORD
}
@@ -0,0 +1,64 @@
# Router for the private network
resource "upcloud_router" "kubernetes" {
name = "${var.prefix}-${var.cluster_name}-router"
}
# Gateway for internet connectivity
resource "upcloud_gateway" "kubernetes" {
name = "${var.prefix}-${var.cluster_name}-gateway"
zone = var.zone
features = ["nat"]
router {
id = upcloud_router.kubernetes.id
}
}
# Private network for the Kubernetes cluster
resource "upcloud_network" "kubernetes" {
name = "${var.prefix}-${var.cluster_name}-network"
zone = var.zone
router = upcloud_router.kubernetes.id
ip_network {
address = var.network_cidr
dhcp = true
dhcp_default_route = true
family = "IPv4"
gateway = cidrhost(var.network_cidr, 1)
}
depends_on = [upcloud_gateway.kubernetes]
}
# Kubernetes cluster
resource "upcloud_kubernetes_cluster" "main" {
name = "${var.prefix}-${var.cluster_name}"
zone = var.zone
network = upcloud_network.kubernetes.id
control_plane_ip_filter = var.control_plane_ip_filter
private_node_groups = true
}
# Node group for worker nodes
resource "upcloud_kubernetes_node_group" "workers" {
cluster = upcloud_kubernetes_cluster.main.id
name = "${var.prefix}-${var.cluster_name}-workers"
node_count = var.node_count
plan = var.node_plan
anti_affinity = var.node_count > 1
dynamic "cloud_native_plan" {
for_each = var.storage_size != null ? [1] : []
content {
storage_size = var.storage_size
}
}
labels = {
prefix = var.prefix
cluster = var.cluster_name
role = "worker"
env = lookup(var.tags, "Environment", "dev")
}
}
@@ -0,0 +1,31 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_id" {
description = "The ID of the Kubernetes cluster"
value = upcloud_kubernetes_cluster.main.id
}
output "cluster_name" {
description = "The name of the Kubernetes cluster"
value = upcloud_kubernetes_cluster.main.name
}
output "network_id" {
description = "The ID of the private network"
value = upcloud_network.kubernetes.id
}
output "network_cidr" {
description = "The CIDR block of the private network"
value = var.network_cidr
}
output "kubernetes_version" {
description = "The Kubernetes version of the cluster"
value = upcloud_kubernetes_cluster.main.version
}
output "zone" {
description = "The zone where the cluster is deployed"
value = var.zone
}
@@ -0,0 +1,8 @@
terraform {
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
@@ -0,0 +1,50 @@
# ─── Cluster ─────────────────────────────────────────────────────────
variable "prefix" {
description = "Prefix for resource names"
type = string
}
variable "cluster_name" {
description = "Name of the Kubernetes cluster"
type = string
default = "main"
}
variable "zone" {
description = "UpCloud zone"
type = string
}
variable "node_plan" {
description = "UpCloud server plan for worker nodes"
type = string
}
variable "node_count" {
description = "Number of worker nodes"
type = number
}
variable "network_cidr" {
description = "CIDR block for the private network"
type = string
default = "10.100.0.0/24"
}
variable "control_plane_ip_filter" {
description = "CIDRs allowed to access the K8s API"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "storage_size" {
description = "Storage size in GB for worker nodes (overrides plan default via cloud_native_plan block)"
type = number
default = null
}
variable "tags" {
description = "Labels to apply to resources"
type = map(string)
}
+17
View File
@@ -0,0 +1,17 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst-prod"
zone = "no-svg1"
node_plan = "CLOUDNATIVE-4xCPU-8GB"
node_count = 4
storage_size = 30
network_cidr = "10.100.0.0/24"
control_plane_ip_filter = ["0.0.0.0/0"] # TODO: restrict to known CIDRs
tags = {
Environment = "prod"
ManagedBy = "tofu"
}
}
+13
View File
@@ -0,0 +1,13 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_id" {
value = module.cluster.cluster_id
}
output "cluster_name" {
value = module.cluster.cluster_name
}
output "zone" {
value = module.cluster.zone
}
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
provider "upcloud" {
# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORD
}
+116
View File
@@ -0,0 +1,116 @@
# =============================================================================
# UpCloud Workload Cluster
# =============================================================================
# A lean UCS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env upcloud-workload
# ./deploy-workload.sh --env upcloud-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names"
type = string
default = "clst-workload"
}
variable "zone" {
description = "UpCloud zone"
type = string
default = "no-svg1"
}
variable "node_plan" {
description = "UpCloud server plan for worker nodes"
type = string
default = "2xCPU-4GB"
}
variable "node_count" {
description = "Number of worker nodes"
type = number
default = 2
}
variable "network_cidr" {
description = "CIDR block for the private network"
type = string
default = "10.110.0.0/24"
}
variable "control_plane_ip_filter" {
description = "CIDRs allowed to access the K8s API"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "tags" {
description = "Labels to apply to resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
# ─── Networking ───────────────────────────────────────────────────────
resource "upcloud_router" "kubernetes" {
name = "${var.prefix}-workload-router"
}
resource "upcloud_gateway" "kubernetes" {
name = "${var.prefix}-workload-gateway"
zone = var.zone
features = ["nat"]
router {
id = upcloud_router.kubernetes.id
}
}
resource "upcloud_network" "kubernetes" {
name = "${var.prefix}-workload-network"
zone = var.zone
router = upcloud_router.kubernetes.id
ip_network {
address = var.network_cidr
dhcp = true
dhcp_default_route = true
family = "IPv4"
gateway = cidrhost(var.network_cidr, 1)
}
depends_on = [upcloud_gateway.kubernetes]
}
# ─── Kubernetes Cluster ───────────────────────────────────────────────
resource "upcloud_kubernetes_cluster" "main" {
name = "${var.prefix}-workload"
zone = var.zone
network = upcloud_network.kubernetes.id
control_plane_ip_filter = var.control_plane_ip_filter
private_node_groups = true
}
resource "upcloud_kubernetes_node_group" "workers" {
cluster = upcloud_kubernetes_cluster.main.id
name = "${var.prefix}-workload-workers"
node_count = var.node_count
plan = var.node_plan
anti_affinity = var.node_count > 1
labels = {
prefix = var.prefix
cluster = "workload"
role = "worker"
env = lookup(var.tags, "Environment", "workload")
}
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = upcloud_kubernetes_cluster.main.name }
output "cluster_id" { value = upcloud_kubernetes_cluster.main.id }
output "zone" { value = var.zone }
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
provider "upcloud" {
# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORD
}
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOFU_ROOT="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$TOFU_ROOT")"
usage() {
cat <<EOF
Usage: $0 <cluster> --envtype <dev|prod|workload>
Fetch (or reuse) a kubeconfig for the given cluster.
Platform is read from the cluster prefix (<platform>-...).
Env type must be supplied explicitly — it is no longer inferred
from the cluster name, so names like 'upc-forte-group' work.
Examples:
$0 aks-dev --envtype dev
$0 upc-forte-group --envtype prod
$0 eks-workload --envtype workload
EOF
exit "${1:-0}"
}
CLUSTER=""
ENVTYPE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--envtype) ENVTYPE="${2:-}"; shift 2 ;;
--envtype=*) ENVTYPE="${1#*=}"; shift ;;
-h|--help) usage 0 ;;
-*) echo "Unknown option: $1"; usage 1 ;;
*)
if [[ -z "$CLUSTER" ]]; then
CLUSTER="$1"; shift
else
echo "Error: unexpected argument '$1'"; usage 1
fi
;;
esac
done
[[ -z "$CLUSTER" ]] && { echo "Error: <cluster> argument required"; usage 1; }
[[ -z "$ENVTYPE" ]] && { echo "Error: --envtype <dev|prod|workload> required"; usage 1; }
case "$ENVTYPE" in
dev|prod|workload) ;;
*) echo "Error: invalid --envtype '$ENVTYPE'. Expected: dev, prod, workload"; exit 1 ;;
esac
PLATFORM="${CLUSTER%%-*}"
ENV="$ENVTYPE"
case "$PLATFORM" in
aks|eks|gke|upc) ;;
*) echo "Error: unknown platform '$PLATFORM'. Expected: aks, eks, gke, upc"; exit 1 ;;
esac
KUBECONFIG_FILE="$PROJECT_ROOT/private/$CLUSTER/kubeconfig"
if [[ -f "$KUBECONFIG_FILE" ]]; then
echo "Kubeconfig already exists: $KUBECONFIG_FILE"
echo ""
echo " export KUBECONFIG=$KUBECONFIG_FILE"
else
echo "No cached kubeconfig. Fetching from platform..."
# Load platform credentials
ENV_FILE="$TOFU_ROOT/configs/$PLATFORM.env"
if [[ -f "$ENV_FILE" ]]; then
set -a; source "$ENV_FILE"; set +a
fi
TOFU_DIR="$TOFU_ROOT/platforms/$PLATFORM/$ENV"
mkdir -p "$(dirname "$KUBECONFIG_FILE")"
case "$PLATFORM" in
aks)
cd "$TOFU_DIR"
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "$CLUSTER-rg")
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")
az aks get-credentials --resource-group "$RG" --name "$NAME" --file "$KUBECONFIG_FILE" --overwrite-existing
;;
eks)
cd "$TOFU_DIR"
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")
REGION=$(tofu output -raw aws_region 2>/dev/null || echo "${AWS_REGION:-eu-west-1}")
aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE"
;;
gke)
cd "$TOFU_DIR"
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")
REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}")
PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}")
gcloud container clusters get-credentials "$NAME" --region "$REGION" --project "$PROJECT"
cp ~/.kube/config "$KUBECONFIG_FILE"
;;
upc)
cd "$TOFU_DIR"
CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}")
upctl kubernetes config "$CLUSTER_ID" > "$KUBECONFIG_FILE"
;;
esac
chmod 600 "$KUBECONFIG_FILE"
echo "Kubeconfig saved: $KUBECONFIG_FILE"
echo ""
echo " export KUBECONFIG=$KUBECONFIG_FILE"
fi
+263
View File
@@ -0,0 +1,263 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOFU_ROOT="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$TOFU_ROOT")"
# ─── Usage ────────────────────────────────────────────────────────────
usage() {
cat <<EOF
Usage: $0 <cluster> --envtype <dev|prod|workload> [options]
Provision a Kubernetes cluster using OpenTofu.
Cluster name is opaque — platform is read from its prefix
(<platform>-...), env is taken from --envtype.
Platforms (inferred from cluster prefix):
aks | eks | gke | upc
Env types (required via --envtype):
dev Platform cluster, development
prod Platform cluster, production
workload Lean cluster for application workloads (no managed data
services — those run on the platform cluster)
Options:
--envtype <type> dev | prod | workload (required)
--plan Plan only, don't apply
--destroy Destroy the cluster (use teardown-cluster.sh instead)
--auto Skip confirmation prompts
-h, --help Show this help
Examples:
$0 aks-dev --envtype dev
$0 eks-prod --envtype prod --plan
$0 upc-forte-group --envtype prod --auto
$0 upc-workload --envtype workload
Prerequisites:
- tofu, kubectl, helm installed
- Platform credentials in .tofu/configs/<platform>.env
- Cluster config in clusters/<cluster>.yaml
After provisioning, run:
./bootstrap.sh <cluster>
EOF
exit "${1:-0}"
}
# ─── Parse arguments ──────────────────────────────────────────────────
CLUSTER=""
ENVTYPE=""
PLAN_ONLY=false
DESTROY=false
AUTO_APPROVE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--plan) PLAN_ONLY=true; shift ;;
--destroy) DESTROY=true; shift ;;
--auto) AUTO_APPROVE=true; shift ;;
--envtype) ENVTYPE="${2:-}"; shift 2 ;;
--envtype=*) ENVTYPE="${1#*=}"; shift ;;
-h|--help) usage 0 ;;
-*) echo "Unknown option: $1"; usage 1 ;;
*)
if [[ -z "$CLUSTER" ]]; then
CLUSTER="$1"
else
echo "Error: unexpected argument '$1'"
usage 1
fi
shift
;;
esac
done
[[ -z "$CLUSTER" ]] && { echo "Error: <cluster> argument required"; usage 1; }
[[ -z "$ENVTYPE" ]] && { echo "Error: --envtype <dev|prod|workload> required"; usage 1; }
case "$ENVTYPE" in
dev|prod|workload) ;;
*) echo "Error: invalid --envtype '$ENVTYPE'. Expected: dev, prod, workload"; exit 1 ;;
esac
# ─── Resolve platform + env ───────────────────────────────────────────
PLATFORM="${CLUSTER%%-*}" # cluster prefix → platform (e.g. upc-forte-group → upc)
ENV="$ENVTYPE" # env comes from --envtype, not the cluster name
case "$PLATFORM" in
aks|eks|gke|upc) ;;
*) echo "Error: unknown platform '$PLATFORM'. Expected: aks, eks, gke, upc"; exit 1 ;;
esac
TOFU_DIR="$TOFU_ROOT/platforms/$PLATFORM/$ENV"
if [[ ! -d "$TOFU_DIR" ]]; then
echo "Error: tofu directory not found: $TOFU_DIR"
echo "Available environments for $PLATFORM:"
ls -1 "$TOFU_ROOT/platforms/$PLATFORM/" 2>/dev/null | grep -v modules || echo " (none)"
exit 1
fi
echo "========================================="
echo " Kubernetes Cluster Setup"
echo "========================================="
echo ""
echo " Cluster: $CLUSTER"
echo " Platform: $PLATFORM"
echo " Env: $ENV"
echo " Tofu dir: $TOFU_DIR"
echo ""
# ─── Prerequisites ────────────────────────────────────────────────────
echo "=== Checking Prerequisites ==="
command -v tofu >/dev/null 2>&1 || { echo "Error: tofu is not installed."; exit 1; }
command -v kubectl >/dev/null 2>&1 || { echo "Error: kubectl is not installed."; exit 1; }
command -v helm >/dev/null 2>&1 || { echo "Error: helm is not installed."; exit 1; }
echo " tofu, kubectl, helm: OK"
# ─── Load platform credentials ────────────────────────────────────────
ENV_FILE="$TOFU_ROOT/configs/$PLATFORM.env"
if [[ -f "$ENV_FILE" ]]; then
echo " Loading credentials from configs/$PLATFORM.env"
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
else
echo " Warning: $ENV_FILE not found — using existing environment/CLI auth"
echo " Copy configs/$PLATFORM.env.example → configs/$PLATFORM.env to configure"
fi
# ─── Load cluster config (if exists) ──────────────────────────────────
CLUSTER_CONFIG="$PROJECT_ROOT/clusters/$CLUSTER.yaml"
if [[ -f "$CLUSTER_CONFIG" ]]; then
echo " Loading cluster config from clusters/$CLUSTER.yaml"
if command -v yq >/dev/null 2>&1; then
eval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"
echo " Cluster name: ${CLUSTER_clusterName:-$CLUSTER}"
else
echo " Warning: yq not installed — cluster config not loaded"
fi
else
echo " Warning: $CLUSTER_CONFIG not found — using defaults"
fi
echo ""
# ─── Run OpenTofu ─────────────────────────────────────────────────────
cd "$TOFU_DIR"
echo "=== Initializing OpenTofu ==="
tofu init
echo ""
if $DESTROY; then
echo "=== Planning Destruction ==="
tofu plan -destroy -out=tfplan
if ! $AUTO_APPROVE; then
echo ""
read -rp "DESTROY cluster $CLUSTER? This is irreversible. (yes/no) " REPLY
[[ "$REPLY" == "yes" ]] || { echo "Cancelled."; exit 1; }
fi
echo "Destroying infrastructure..."
tofu apply tfplan
echo ""
echo "=== Cluster $CLUSTER Destroyed ==="
elif $PLAN_ONLY; then
echo "=== Planning Infrastructure ==="
tofu plan
echo ""
echo "=== Plan complete (--plan mode, no changes applied) ==="
else
echo "=== Planning Infrastructure ==="
tofu plan -out=tfplan
if ! $AUTO_APPROVE; then
echo ""
read -rp "Apply this plan for $CLUSTER? (y/n) " -n 1 REPLY
echo
[[ "$REPLY" =~ ^[Yy]$ ]] || { echo "Cancelled."; exit 1; }
fi
echo "Applying infrastructure..."
tofu apply tfplan
# ─── Save kubeconfig ──────────────────────────────────────────────
KUBECONFIG_DIR="$PROJECT_ROOT/private/$CLUSTER"
mkdir -p "$KUBECONFIG_DIR"
KUBECONFIG_FILE="$KUBECONFIG_DIR/kubeconfig"
echo ""
echo "=== Saving Kubeconfig ==="
case "$PLATFORM" in
aks)
if tofu output -raw kubeconfig > "$KUBECONFIG_FILE" 2>/dev/null; then
echo " Saved from tofu output"
else
echo " Fetching from Azure CLI..."
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")
az aks get-credentials --resource-group "$RG" --name "$NAME" --file "$KUBECONFIG_FILE" --overwrite-existing
fi
;;
eks)
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")
REGION=$(tofu output -raw aws_region 2>/dev/null || echo "${AWS_REGION:-eu-west-1}")
aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE"
;;
gke)
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")
REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}")
PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}")
gcloud container clusters get-credentials "$NAME" --region "$REGION" --project "$PROJECT" 2>/dev/null \
&& cp ~/.kube/config "$KUBECONFIG_FILE" \
|| echo " Warning: could not fetch kubeconfig via gcloud"
;;
upc)
if tofu output -raw kubeconfig > "$KUBECONFIG_FILE" 2>/dev/null; then
echo " Saved from tofu output"
else
CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}")
if [[ -n "$CLUSTER_ID" ]]; then
upctl kubernetes config "$CLUSTER_ID" > "$KUBECONFIG_FILE"
else
echo " Warning: could not determine cluster ID for kubeconfig"
fi
fi
;;
esac
if [[ -f "$KUBECONFIG_FILE" ]]; then
chmod 600 "$KUBECONFIG_FILE"
echo " Kubeconfig: $KUBECONFIG_FILE"
fi
# ─── Wait for nodes ──────────────────────────────────────────────
echo ""
echo "=== Waiting for Cluster Nodes ==="
export KUBECONFIG="$KUBECONFIG_FILE"
if kubectl wait --for=condition=Ready nodes --all --timeout=300s 2>/dev/null; then
echo " All nodes ready"
else
echo " Warning: nodes not ready within timeout — check cluster status"
fi
# ─── Summary ─────────────────────────────────────────────────────
echo ""
echo "========================================="
echo " Cluster $CLUSTER Provisioned"
echo "========================================="
echo ""
echo " Kubeconfig: $KUBECONFIG_FILE"
echo ""
echo " Next steps:"
echo " export KUBECONFIG=$KUBECONFIG_FILE"
echo " ./bootstrap.sh $CLUSTER"
echo ""
fi
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Delegate to setup-cluster.sh with --destroy flag
exec "$SCRIPT_DIR/setup-cluster.sh" "$@" --destroy
-460
View File
@@ -1,460 +0,0 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Documentation for the GitOps-managed Kubernetes cluster">
<link rel="icon" href="/assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.6">
<title>K8s Launchpad</title>
<link rel="stylesheet" href="/assets/stylesheets/main.484c7ddc.min.css">
<link rel="stylesheet" href="/assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("/",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="indigo">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="/." title="K8s Launchpad" class="md-header__button md-logo" aria-label="K8s Launchpad" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
K8s Launchpad
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="indigo" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="indigo" data-md-color-accent="indigo" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://git.forteapps.net/Forte/launchpad" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
Forte/launchpad
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="/." title="K8s Launchpad" class="md-nav__button md-logo" aria-label="K8s Launchpad" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
K8s Launchpad
</label>
<div class="md-nav__source">
<a href="https://git.forteapps.net/Forte/launchpad" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
Forte/launchpad
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="/." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/GITOPS-ARCHITECTURE/" class="md-nav__link">
<span class="md-ellipsis">
GitOps Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/DEVELOPER-GUIDE/" class="md-nav__link">
<span class="md-ellipsis">
Developer Guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/OPERATIONS-RUNBOOK/" class="md-nav__link">
<span class="md-ellipsis">
Operations Runbook
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/REFERENCE/" class="md-nav__link">
<span class="md-ellipsis">
Technical Reference
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1>404 - Not found</h1>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "/", "features": ["navigation.instant", "navigation.sections", "navigation.top", "search.highlight", "content.code.copy"], "search": "/assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="/assets/javascripts/bundle.79ae519e.min.js"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+542
View File
@@ -0,0 +1,542 @@
# Kubernetes Cluster - GitOps Configuration
> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for multi-cloud Kubernetes (UpCloud, AWS EKS, Azure AKS, GCP GKE)
[![GitOps](https://img.shields.io/badge/GitOps-ArgoCD-blue)](https://argoproj.github.io/cd/)
[![Kubernetes](https://img.shields.io/badge/Kubernetes-Multi--Cloud-orange)]()
---
## 📚 Complete Documentation
**New developers and operators**: Please refer to our comprehensive documentation for detailed guides and references:
### 🎯 [**START HERE: Documentation Index**](docs/README.md)
| Document | Description | Audience |
|----------|-------------|----------|
| **[GitOps Architecture](docs/GITOPS-ARCHITECTURE.md)** | System architecture, repository structure, GitOps workflows, security model | Everyone (start here) |
| **[Developer Guide](docs/DEVELOPER-GUIDE.md)** | Local setup, deploying apps, managing secrets, troubleshooting | Developers |
| **[Operations Runbook](docs/OPERATIONS-RUNBOOK.md)** | Cluster bootstrap, day-to-day operations, incident response, maintenance | Platform Engineers, SREs |
| **[Technical Reference](docs/REFERENCE.md)** | Component specs, Helm charts, ArgoCD config, Kyverno policies, API docs | Everyone (reference) |
---
## 🚀 Quick Start
### For New Developers
```bash
# 1. Clone repositories
git clone https://git.forteapps.net/Forte/launchpad.git
git clone ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
# 2. Read the guides
# - Start: docs/GITOPS-ARCHITECTURE.md
# - Follow: docs/DEVELOPER-GUIDE.md
# 3. Deploy your first app (see Developer Guide)
```
### For Operators
```bash
# 1. Bootstrap new cluster
./bootstrap.sh
# 2. Verify deployment
kubectl get applications -n argocd
kubectl get pods --all-namespaces
# 3. Read Operations Runbook for day-to-day tasks
```
---
## 📋 Overview
This repository contains the complete GitOps configuration for our Kubernetes cluster, using the **App-of-Apps pattern** with ArgoCD.
### What's Inside
- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets, Homepage (platform dashboard)
- **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP
- **Policies**: Kyverno security policies for secret management, namespace controls, pod verification
- **Monitoring**: Full observability stack with metrics, logs, traces, and alerting
- **Secrets**: Sealed Secrets for secure Git storage
### Key Features
**GitOps-Native**: Git is the single source of truth
**Auto-Sync**: Changes automatically deployed (60s reconciliation)
**Self-Healing**: Manual cluster changes are reverted
**Multi-Source**: Separate chart templates from configuration
**Policy Enforcement**: Kyverno ensures security and compliance
**Authentication**: Automatic sidecar injection (token & OIDC support)
**TLS Everywhere**: Automatic Let's Encrypt certificates
**Full Observability**: Prometheus, Grafana, Loki, Tempo integration
---
## 🗂️ Repository Structure
```
.
├── bootstrap.sh # Cluster initialization (ArgoCD + GitOps)
├── _app-of-apps-{cluster}.yaml # Root ArgoCD Application (per cluster)
├── .tofu/ # Infrastructure provisioning (OpenTofu)
│ ├── platforms/ # Per-platform IaC (one dir per cloud)
│ │ ├── aks/ # Azure AKS (modules/ + dev/ + prod/ + workload/)
│ │ ├── eks/ # AWS EKS
│ │ ├── gke/ # GCP GKE
│ │ └── upc/ # UpCloud
│ ├── configs/ # Platform credentials (git-ignored)
│ │ └── *.env.example # Template for each platform
│ └── scripts/ # Cluster lifecycle scripts
│ ├── setup-cluster.sh # Create cluster: ./setup-cluster.sh aks-dev
│ ├── teardown-cluster.sh
│ └── get-kubeconfig.sh
├── clusters/ # Cluster metadata (domain, trustedIPs, etc.)
├── infra/ # Infrastructure ArgoCD Applications (Kustomize multi-cluster)
│ ├── base/ # Base ArgoCD Application manifests (one dir per component)
│ │ ├── kustomization.yaml # Aggregates all component subdirectories
│ │ ├── traefik-application/
│ │ │ ├── kustomization.yaml
│ │ │ └── traefik-application.yaml
│ │ ├── keycloak/
│ │ │ ├── kustomization.yaml
│ │ │ └── keycloak.yaml
│ │ ├── grafana/
│ │ ├── prometheus/
│ │ ├── ... # Each component in its own subdirectory
│ │ └── secrets/
│ ├── overlays/ # Per-cluster overrides (Kustomize)
│ │ ├── upc-dev/ # UpCloud Dev — includes all base components
│ │ ├── upc-prod/ # UpCloud Prod — all components + patches
│ │ ├── aks-dev/ # Azure AKS Dev — selective components only
│ │ ├── aks-prod/ # Azure AKS Prod
│ │ ├── eks-dev/ # AWS EKS Dev
│ │ ├── eks-prod/ # AWS EKS Prod
│ │ ├── gke-dev/ # GCP GKE Dev
│ │ └── gke-prod/ # GCP GKE Prod
│ ├── dashboards/ # Grafana dashboard ConfigMaps
│ └── values/ # Helm value overrides
│ ├── base/ # Shared cloud-agnostic values
│ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing)
│ ├── upc-prod/ # UpCloud Prod
│ ├── eks-dev/ # AWS EKS Dev
│ ├── eks-prod/ # AWS EKS Prod
│ ├── aks-dev/ # Azure AKS Dev
│ ├── aks-prod/ # Azure AKS Prod
│ ├── gke-dev/ # GCP GKE Dev
│ └── gke-prod/ # GCP GKE Prod
├── apps/ # Business Applications (Kustomize, same pattern as infra)
│ ├── base/ # One subdirectory per app
│ │ ├── kustomization.yaml
│ │ ├── musicman/
│ │ ├── mcp10x/
│ │ ├── dot-ai-stack/
│ │ ├── ts-mcp/
│ │ └── argo-mcp/
│ └── overlays/ # Per-cluster: cherry-pick or include all
│ ├── upc-dev/ # All apps
│ ├── upc-prod/ # All apps + patches
│ └── aks-dev/ # Selective apps only
├── cluster-resources/ # Cluster-wide Kubernetes resources
│ ├── letsencrypt-issuer.yaml
│ ├── kyverno-config.yaml
│ ├── *-sealed.yaml # Sealed secrets
│ └── policies/ # Kyverno policies
│ ├── secret-cloner.yaml
│ ├── default-ns-blocker.yaml
│ ├── bare-pod-cleaner.yaml
│ └── auth-sidecar-injector.yaml
├── secrets/ # Application secrets (sealed)
│ └── *-credentials-sealed.yaml
├── private/ # Local-only files (Git-ignored)
│ └── *.yaml # Unsealed secrets (never committed)
└── docs/ # 📚 Comprehensive documentation
├── README.md # Documentation index
├── GITOPS-ARCHITECTURE.md # Architecture guide
├── DEVELOPER-GUIDE.md # Developer onboarding
├── OPERATIONS-RUNBOOK.md # Operations procedures
└── REFERENCE.md # Technical reference
```
**See [GitOps Architecture - Repository Structure](docs/GITOPS-ARCHITECTURE.md#repository-structure) for detailed explanation.**
---
## 🏗️ Architecture
### Three-Repository Pattern
| Repository | Purpose | Who Edits | How Often |
|------------|---------|-----------|-----------|
| **[launchpad](https://git.forteapps.net/Forte/launchpad)** (this repo) | ArgoCD Applications, cluster resources | Platform / DevOps engineers | ✅ Often |
| **[forte-helm](https://git.forteapps.net/Forte/forte-helm)** | Generic Helm chart templates | Platform engineers | ❌ Rarely |
| **[helm-prod-values](ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git)** | App-specific configuration & versions | Developers / CI pipelines | ✅ Sometimes |
### GitOps Workflow
```
Developer commits code → CI/CD builds image → Updates helm-prod-values → ArgoCD syncs → Deployed to cluster
```
**Learn more**: [GitOps Architecture - GitOps Workflow](docs/GITOPS-ARCHITECTURE.md#gitops-workflow)
---
## 🔧 Common Tasks
### Deploy a New Application
**See detailed guide**: [Developer Guide - Deploying Your First Application](docs/DEVELOPER-GUIDE.md#deploying-your-first-application)
**Quick version**:
1. Create `apps/myapp.yaml` (ArgoCD Application manifest)
2. Create `helm-prod-values/myapp/values.yaml` (configuration)
3. Create sealed secrets if needed
4. Commit and push - ArgoCD auto-syncs!
### Update an Existing Application
**See detailed guide**: [Developer Guide - Updating an Existing Application](docs/DEVELOPER-GUIDE.md#updating-an-existing-application)
**Quick version**:
- **Update code**: Push to app repo → CI/CD updates image tag in helm-prod-values
- **Update config**: Edit `helm-prod-values/myapp/values.yaml` → commit → push
### Manage Secrets
**See detailed guide**: [Developer Guide - Working with Secrets](docs/DEVELOPER-GUIDE.md#working-with-secrets)
```bash
# Create plain secret
kubectl create secret generic myapp-creds \
--from-literal=KEY=value \
--dry-run=client -o yaml > private/myapp-creds.yaml
# Seal it
kubeseal --format=yaml --cert=pub-cert.pem \
< private/myapp-creds.yaml > secrets/myapp-creds-sealed.yaml
# Commit sealed version
git add secrets/myapp-creds-sealed.yaml
git commit -m "Add myapp credentials"
git push
```
### Enable Authentication
**See detailed guide**: [Developer Guide - Enabling Authentication](docs/DEVELOPER-GUIDE.md#enabling-authentication-for-applications)
**Quick version**:
```yaml
# In helm-prod-values/myapp/values.yaml
# Token-based auth (simple)
auth:
enabled: true
type: token
tokens:
- your-secret-token-here
# OIDC auth (SSO)
auth:
enabled: true
type: oidc
oidc:
authority: https://auth.example.com/realms/master
clientId: myapp
```
Then create OIDC secret (if using OIDC):
```bash
kubectl create secret generic auth-oidc \
--from-literal=client-secret=your-oidc-secret \
--from-literal=cookie-secret=$(openssl rand -hex 32) \
--namespace=myapp | \
kubeseal --format=yaml --cert=pub-cert.pem --namespace=myapp | \
kubectl apply -f -
```
### Bootstrap Cluster
**See detailed guide**: [Operations Runbook - Cluster Bootstrap](docs/OPERATIONS-RUNBOOK.md#cluster-bootstrap)
```bash
# Initialize new cluster
./bootstrap.sh
# Verify
kubectl get applications -n argocd
kubectl get pods --all-namespaces
```
---
## 🛠️ Quick Reference
### Monitor Applications
```bash
# List all ArgoCD applications
kubectl get applications -n argocd
# Watch sync status
kubectl get applications -n argocd -w
# Check specific application
kubectl describe application myapp -n argocd
# View application logs
kubectl logs -n myapp <pod-name>
```
### Access UIs
```bash
# ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Access: https://localhost:8080 (no auth required)
# Grafana
kubectl port-forward -n monitoring svc/grafana 3000:80
# Access: http://localhost:3000
# Prometheus
kubectl port-forward -n monitoring svc/prometheus-server 9090:80
# Access: http://localhost:9090
```
### Troubleshooting
```bash
# Check pod status
kubectl get pods -n myapp
# View pod logs
kubectl logs -n myapp <pod-name>
# Check pod events
kubectl describe pod -n myapp <pod-name>
# Check ArgoCD sync errors
kubectl describe application myapp -n argocd
# Force sync
kubectl patch application myapp -n argocd \
--type merge -p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}'
```
**Full troubleshooting guide**: [Developer Guide - Troubleshooting](docs/DEVELOPER-GUIDE.md#troubleshooting)
---
## 🔐 Security
### Secret Management
- ✅ Sealed Secrets for Git storage
- ✅ Kyverno auto-clones secrets to namespaces
- ❌ Never commit plain secrets
### Network Security
- ✅ All traffic TLS-encrypted (Let's Encrypt)
- ✅ HTTP → HTTPS redirect
- ✅ Traefik IngressRoute per application
### Policy Enforcement
- ✅ Kyverno policies for security
- ✅ Default namespace blocked
- ✅ Bare pods not allowed
- ✅ Optional authentication sidecar injection
**Learn more**: [GitOps Architecture - Security Model](docs/GITOPS-ARCHITECTURE.md#security-model)
---
## 📊 Infrastructure Components
| Component | Purpose | Namespace | Replicas |
|-----------|---------|-----------|----------|
| **ArgoCD** | GitOps controller | `argocd` | 1 |
| **Traefik** | Ingress controller | `traefik` | 2 |
| **Cert-Manager** | TLS certificates | `cert-manager` | 1 |
| **Kyverno** | Policy engine | `kyverno` | 1 |
| **Sealed Secrets** | Secret encryption | `kube-system` | 1 |
| **Prometheus** | Metrics | `monitoring` | 1 |
| **Grafana** | Dashboards | `monitoring` | 1 |
| **Loki** | Logs | `monitoring` | 1 |
| **Tempo** | Distributed tracing | `monitoring` | 1 |
| **Fluent-Bit** | Log shipping | `monitoring` | DaemonSet |
| **OpenCost** | Cost monitoring | `monitoring` | 1 |
| **Renovate** | Dependency updates | `renovate` | CronJob |
**Full specs**: [Technical Reference - Infrastructure Components](docs/REFERENCE.md#infrastructure-components)
---
## 🌐 Domains & Networking
- **Local development**: `*.127.0.0.1.nip.io`
- **Production**: `*.forteapps.net`
- **DNS**: Manual configuration (contact platform team)
- **TLS**: Automatic via Let's Encrypt
---
## 📖 Key Concepts
### App-of-Apps Pattern
`_app-of-apps-{cluster}.yaml` is the root Application that manages all other Applications in `infra/`. Each component in `infra/base/` lives in its own subdirectory (e.g., `infra/base/grafana/`). Overlays can either include **all** components (via `../../base`) or **cherry-pick** specific ones (via `../../base/grafana`, `../../base/prometheus`, etc.). Per-cluster patches swap Helm value file paths. Supported clusters: `upc-dev`, `upc-prod`, `eks-dev`, `eks-prod`, `aks-dev`, `aks-prod`, `gke-dev`, `gke-prod`.
### Multi-Source Pattern
Applications reference both:
1. **Helm charts** from `forte-helm` (templates)
2. **Values** from `helm-prod-values` (configuration)
This separates reusable templates from environment-specific config.
### Sync Waves
Applications deploy in order using `argocd.argoproj.io/sync-wave`:
- Wave `-1`: Namespaces
- Wave `0`: Kyverno (policies)
- Wave `1`: Infrastructure
- Wave `2+`: Applications
### Auto-Sync & Self-Heal
- **Auto-Sync**: ArgoCD automatically deploys Git changes (60s polling)
- **Self-Heal**: Manual cluster changes are reverted to match Git
- **Prune**: Deleted resources in Git are removed from cluster
**Learn more**: [GitOps Architecture - GitOps Workflow](docs/GITOPS-ARCHITECTURE.md#gitops-workflow)
---
## ⚙️ Configuration
### ArgoCD Settings
- **Reconciliation**: Every 60 seconds
- **Sync timeout**: 5 minutes per application
- **Retry policy**: 5 attempts with exponential backoff
- **Authentication**: Disabled (internal use only)
### Application Defaults
- **Auto-sync**: Enabled
- **Self-heal**: Enabled
- **Prune**: Enabled
- **Validation**: Server-side validation enabled
- **Server-side apply**: Enabled
**Full configuration**: [Technical Reference - ArgoCD Configuration](docs/REFERENCE.md#argocd-configuration)
---
## 🆘 Getting Help
### Documentation
1. **Start here**: [Documentation Index](docs/README.md)
2. **For development**: [Developer Guide](docs/DEVELOPER-GUIDE.md)
3. **For operations**: [Operations Runbook](docs/OPERATIONS-RUNBOOK.md)
4. **For reference**: [Technical Reference](docs/REFERENCE.md)
### Support
- **Slack**: #platform-support
- **Issues**: Contact platform team
- **Emergencies**: Escalate via Slack
### Common Questions
| Question | Answer |
|----------|--------|
| How do I deploy an app? | [Developer Guide - Deploying Your First Application](docs/DEVELOPER-GUIDE.md#deploying-your-first-application) |
| How do I manage secrets? | [Developer Guide - Working with Secrets](docs/DEVELOPER-GUIDE.md#working-with-secrets) |
| App won't sync? | [Developer Guide - Troubleshooting](docs/DEVELOPER-GUIDE.md#troubleshooting) |
| How do I bootstrap a cluster? | [Operations Runbook - Cluster Bootstrap](docs/OPERATIONS-RUNBOOK.md#cluster-bootstrap) |
| Where are the logs? | [Operations Runbook - Monitoring & Alerting](docs/OPERATIONS-RUNBOOK.md#monitoring--alerting) |
---
## 🤝 Contributing
### Adding a New Application
1. Read [Developer Guide - Deploying Your First Application](docs/DEVELOPER-GUIDE.md#deploying-your-first-application)
2. Create ArgoCD Application manifest in `apps/`
3. Create Helm values in `helm-prod-values/`
4. Create sealed secrets if needed
5. Commit and push - ArgoCD handles the rest!
### Modifying Infrastructure
1. Read [Operations Runbook](docs/OPERATIONS-RUNBOOK.md)
2. Update relevant files in `infra/` or `cluster-resources/`
3. Test changes in isolated namespace if possible
4. Commit and push
5. Monitor sync status in Slack/ArgoCD UI
### Updating Documentation
Documentation lives in `docs/`. To update:
1. Edit relevant markdown file
2. Update "Last Updated" date
3. Submit PR or push directly
4. Notify team of significant changes
---
## 📝 Notes
### Current Environment
- **Provider**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE)
- **Active clusters**: UpCloud (upc-dev, upc-prod)
- **Environment**: Production (internal use only)
- **Auth**: Disabled for ArgoCD (internal access)
- **Backup**: Gitea daily backup to S3-compatible storage
### Known Limitations
- Secret rotation not automated
- DNS management is manual
**Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery)
---
## 📚 Additional Resources
### External Documentation
- [ArgoCD Documentation](https://argo-cd.readthedocs.io/)
- [Kyverno Documentation](https://kyverno.io/docs/)
- [Traefik Documentation](https://doc.traefik.io/traefik/)
- [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)
### Related Repositories
- [forte-helm](https://git.forteapps.net/Forte/forte-helm) - Helm chart templates
- [helm-prod-values](git@github.com:fortedigital/helm-prod-values.git) - Application values
---
## 📄 License
Internal use only. Not for public distribution.
---
## 👥 Maintainers
**Platform Team**
- Contact: #platform-support on Slack
- Issues: Create issue in repository or contact team directly
---
**Last Updated**: 2026-04-22
**Documentation Version**: 1.0.0
**🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!**
-4220
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/aks-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/aks-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/eks-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/eks-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/gke-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/gke-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/upc-dev
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/upc-forte-group
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+32
View File
@@ -0,0 +1,32 @@
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infrastructure-apps
namespace: argocd
labels:
app.kubernetes.io/name: infrastructure-apps
app.kubernetes.io/part-of: platform
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: infra/overlays/upc-prod
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+38
View File
@@ -0,0 +1,38 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd-mcp
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels:
app.kubernetes.io/name: argocd-mcp
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/argocd-mcp/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: argocd-mcp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
@@ -0,0 +1,15 @@
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: argocd-mcp-credentials
namespace: argocd-mcp
spec:
encryptedData:
ARGOCD_API_TOKEN: AgCgJkgdm3jJhMZcQviLXHU0T9MVWRw+OaVLS965PpIFSu6HkBwvVuOx0uUKzEtss3y5XjoGzgqrOrkVJqdb7Amiyj88ZIhLImdZzP/FXhH9N4U1jQ+ayPI02EmjhYuuJeNSvbF+K0IwS8nj/5POaCN9CNj5f/px8EmYhZalcFO7szBbpVNiwyGObMIv7DgtjrfROIVWZ1MD2oEjQ7jiqzvi2ynBJtiF7wyX0ZzE6aIYg/eyvZ3dkW0tRrWjFLtaucFoPVto848UeRHsNfQJnuFHGRI4mrnanBI9YsppPqWuHic9fvyLg9fMz9ZTHaPTS5EMbU+e9eshkQqAPLmawjiy2zW8X9MytjTECPVALko8ejj0MTwmTS15as4tXn7FqAxsH5hCHISVHdqQnEhvw9IdUSXFooipgWaQmz4k+zR3TlMU9III3G0PYiFVyjWy2/mfO4Vsg4RoD8i+RVqQINK9EN6OCr3frqMumhMlJLpTX6+8749Qa/5b6aRzvt5ACDixeHXzeJTzbYJrfGNwTrjL5SZvILgzE24g9YocE5/aLp1X4Qyb99qSODkA51uiVKKLJx9K+R1SnHDduxWuh+9tzJXgbWu2PACaMb5qLkpzA6Dx2kF28lIBAYz/yYR1kY2wvpqh7SO2rXSUROCB7RDRLCCk8V40J74/o4HiDzj9DJEmRzJKWerkPlzOxoe32XDvtY3rmZ1t+orXPRuRJ8PDrmhD4Z0+iBDQrxkv3UDvF7ES7NbtCiXQRN1m3v03q9rP4AsB/s0p8Byq94SoUasisDk5mCf1TAdvkgK4C9D4BcqCfVYRFNGk5AgbuIb1Fsz506TWFImEiJp2R+z18KIJ2Uhdz4NfkUhToe8odr1mP6vxzjzv+6Et5+v9CVYZPkoO7JBDwCVRie+H8qroAzG8OvAq9iuptwDFeZuQ9RLbDGtneqMjWj0a3pkdhRddr6uaKus/oBJo2E98afMY1WWRh/19fstkQ0NYeTrJahTNrKtxnHziTO89MVQZ5G7eDEKnQPYELnor6mMZ9CqSqUIT3w==
ARGOCD_BASE_URL: AgA6OxPRQoyPHYpTQoO/vM4xkl0heMdPvIZ6xBxS9+yt9zbqhBCoupixyuP4nxbkwB522GVnC/qG2nNN5kzzWck99nlf0UVJ0uac5x5GtkbZ5s1Rc2u1aE42hMdmqS7MIqp0OR96nY2aWwPPIh2OLIECEf1axLZ6uN1bMEAUN36s05JQUlKMikFfcv1iIqxiVj7U2oz5jNXUGknnXnDp4V7Y68yyXpjzqp8Gm0dCugc4ZLuK3v3IUsKTJ/A+keMbeDC+VGTYmoNJevVhuZaPgcwRDU1v7bIYSjVgvYenOEvdWM3IZs5x4USLWPr9K8a7efSCYRJBZD0/45Au87bIcASXtZzN1ifmaWnI69uqqCLj2dsO1O2RTf0ivzpzRLPJkX1D7yqOnPCyQEAGryFSdyl9w8by6Z801dsJkSf/NM3aymqxu0xhNZ+r1tSSdlt6RKEjFJfrsGD/UmBW6EpfMAw6N34FUc3qpgB7TGW7cK5UbxtQEJbdxhOpySdSnMCfupgIu9x/mQa5tfbHMAc8QTj9lXMuipNrO7n5gIxOG6ra+uM7gaDW/xdksDGNmekTO+C9inqQj/LRXL/MEXdy+LaPRquzE0+YRLjCvRFjv0glK+vR2scd7+bd90Pnv5qJwOPod6PXCREGviiBJvSt62mTxnnritHun77lXwpScFYHhA8Loey5/blUSXnOkScLHGzhUibX6AIRw/nBw3IwfgHHrfVz4xBrrzBCs1eJxQfrELsmondd61MeCz94ERY=
template:
metadata:
creationTimestamp: null
name: argocd-mcp-credentials
namespace: argocd-mcp
@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: auth-oidc
namespace: argocd-mcp
spec:
encryptedData:
client-secret: AgBHbqXV1iuVNc1txIt2VWm+Enm2j7W2Z1WptWvRyLZrEhjJlY9pPxJuDuEIGeMbYsVlp+I79Wl897EYPpzZTo7U/h+JQKtPe78rS9trGN6Y+jE5ID/H+SYqZrWDXkYGWjan2+5OTpuH4Cef7G6faXhDW8iwhch4Ep1ZIDbiHQ2EVmQ24+Ar6/d+RDlcfXvF38oWk1Io3WrDkxzf210nIuX5ZapyDP+hP/0Tk7i75wlJX/HYh664rBo+Plxy2HQweRvmUb9y4/jv6VJtlY7tmud7GF9QRg9tGUV8Aor9qyhZjH6CUx7AATelkcaWFko5h185d/q8AYxDbdguSbvVor5hcfG14YCqi51gDNK23jsGdMYiLkSp1Pgr2hgrWL7T5c2LLd2BMXqG1UXIkhwTukK7oPHiZV9sIbvX//duIQWQHRT795eHO2F1YJXiGeDY7icvqpNhpjVxhb4FRTj5PjOKtjfMWd1BGus7Y0THw91kKaM0i/cmKOwNpNHEqsGMcqL9blRykTQWPNL40/Efvk8QxL6D14394F5FLHHg2rzvgASdroqnIq86UrlR3xmXPHpMpUu1zQwfL4eU31Fvy8FsrjPGNhHZPXvdBzi2U6HvFe+/SFrQm3EhGV3JA7tBTLx2jByzLzZLOuPZ0ndrQwccmuSLOkjSVE+WOwZJQ9X6rGa/dyxe/jTvnx6rZ7C/PutrP6BSqtD8By60vF9/bS76mGTQAMEtnsNfaBTiRiU+W0oAfb43VpZn
cookie-secret: AgA3NHKTzIdz9PgWDLhyZRt8c1SP7oE0cdlyLUjKVBpeozz0YuiPus54w7IdWQcDhtE5KhV3ndjqiu4FIQIGDRil5Am863zCT0qCx71sldql6bhZbUWnYEnUvF2UpayQTajB6+zcEo9IbHta8146Ien3yCuYpfZ0m0RoIVrYCnJFijOurfRieHkQ6nhmjxkFYOha9xStJFoGxKNetdv6uw52+iTMmSOjYtmtwA77uhU4SovrZ2Rs6xp3ySX7bdho0sfu+H/Gyl7/olLBAedVcCBKpZ06efY/SIlP3TGYH4GWN+dxapuCtg+zgTNFOXyWnHdTaeEHrPfVWZnlcy7WOFs8vh1tUsvsmYiR3Pq1OeGPdrayXexM92sKMcqGuX5F6yRv7lE2ikK/rGpNt1NyGOizyrYSWhaRICNtMeFRljEfCgjwsrfzJ28cBsBbAwUg3VTfJLxGOsYZ1oiiO//BO2C4pQtw72fEwpyPkYlSXUgOgnnYZmCvpuc/joHMRyrHIZbkYQ24jDD3a75f+6JP6zXDKYpSsq5g3Sdjhp/SbY30Lr9yMpNq9Lmv7FWj/KCRIBQ38smZTBWkdytvfFqpXyLFrVM/itM6GSgOA0nJnicNn+0p99YFjQzqgXxPdBkAk3Xg7iOX2e4PGPH2a3GkSIzlnhgGi9tuitx4fvQRQZyaFAZcWbCDcFoc9YuRoMbkbC6LAMQyrFj3Rrolp7UjL17IXP8dJEcwwXr2Oz3taTiuTBuMERo1zDzcFcozBarDmRQvcJqcnEtsxxcrne2AjfYf
template:
metadata:
creationTimestamp: null
name: auth-oidc
namespace: argocd-mcp
+6
View File
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- argo-mcp.yaml
- argocdmcp-auth-oidc-sealed.yaml
- argocd-mcp-credentials.yaml
@@ -0,0 +1,18 @@
# SealedSecret created after namespace (sync-wave: 0)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai
spec:
encryptedData:
anthropic-api-key: AgASIRVNs7kIvZ/XwFJ4j9TJ05TW4YlFNzi2lx+6URlvzjTkMignK+y4/HD3wTK37BkIonnOXf/DGJcMgqgxOrHBVu9tLkSamve5qgf+SOZ8jxaqpLX2e5hdmHrgMp7rVWKiOoxM+bvu8Gdve3eMwXX8Eazj7W7vi430LsvW4BmPrQp+A9SOP+hwMlV5r+P5pUogC3hddt5KoSwpgnpOb8fRGlLDpc548eDNBJrhsAZiiFqc8+CycHX6idxWzlWOTPR05LBs7YrTIP/LUXWdyq6UG8yB5D0c9JNndnGo6kSfJxdaxCNO7GhMAOmo4CitzYn/zhICwuHPLKwUYQBcEHVpFxgub0U9fY7s4i0SAH9aF1kk8yqHWaCicHzi6B3AxMZYr+knS5vVNB+uGZmSKgZ7QNE1bF6E4Gg5bXRwk7OVQhuJSBzxVNDoXoJ6stL8ejU+MP5FtI792FxZbNDImu3kqor/t9wB49ZeB9Ngks2yvtlSrQ3G2lxJaOgNBPmKYhAX95JFI3xUSHOMa18ftn+aXmEQTXOHtM/IT8A95gUvIKz8Hrt03CwgZ8E+3lcsjAVsfNjqn/erqV+xi93OYOL6o9ivmTIem3MzsqjKuQ57HKaghnd+Ygdt/WfotRBSgZtCweOkZBV0XKxI1RmCQXk6dce8x4T7FDEeaSQwkc4yZ77LfQJnyVgU1rds9Hqv0RbymHK8JCQEMMb6PcK+Ow068fdj4NP27HkuYMB5JdPp682KIRwGznN2d3+QPXRd/ZHshnixudY7CLirlZl9xD0HssfSBI+uCrFEAln8D93UYbn7trF5gkKWYQsQ07CVZ5Z1qXieqwJWjHRY+oY=
auth-token: AgB65rUY3yJuTTLbGyrCtJPZp8UyEOr+kznSMGvUaZ9PoHq+kIEhazRdVKHa/WGvMWuc4x+XYRuIGj+2KiooyilUPrLcE3bz9fUOAFbRw3+iUh3WAgwK+f3eBUG6/0OoFw+GJ583IRidDW1t2BchMxSM1m+3vmQoF24qj7k9j/lWPsnX2IX3DhM0SomD1xmG+LsQMo2e4vQXB0BxhVDIQ621JTrvUPYzYx0NlPqZO/MtR1JWYS7WBTvegvgeBKLxfy9KLnqsuzu7rc2t9BYt7TRqvBg/prrKPdSV6Ei4GOZq+AcG15iOKXhsj8SxejPpM0QDemFTRQkdfz+k8ms4/SM0eylr5fEaa99TMTvjYGCfjJJyRcVm5Ef/XdmXiM3OI1u+9QNPiqXh1zmtp0yQMZ7nRE70kKQ2MHVhEmSUkBjovybIzLk4OL1v0FGDDqa1BN9KmNEBlo8H7DqXzfamVoNPyuuO625B3HgSeR3Udq8hngy9W/wiolmMNSc9C6vBA0HzE7Mkq79fHh/ruTi5zOJLfyEP7KQQqlNKfEtDFQfabaV9ERthQjuUs8ZIrdfCTOyQkrmmGY/sH5yodLRSYSEpHFrhPepwHS7lGzbVucW3Fn34D4OxJXyXQQObZKybLV7g6Oglf2Pdn11CEu/4+19RlykuOlm1dlj852a5NWWmmX319laLGlEd1BpG4cZyc9UZud68dGumwzrUupUMTLlJx8bR5rLgqaMpQMluwvq8MQbBXm4ySOcQ2jQhjw==
openai-api-key: AgBc4cSrd0riSxKPrp7AqrNYMDAZJTe3aCjTOwEb5pS/KARkgoEGz+0ZhLXa2bcCsPlQzqRzZ1c1cUCnBACkp1xoMfhSOoIJUWJQHRgZx5kEDhNIE/M8PQe8es7jYsW7/ui6iK8uj3Ljk21r8l/ctnP/KiKyML4553el28Ya7XsPQynRvpVexFC4BoJw50nkauLnvl3in0cKmEVAkMWorUP3Etapaj4DWkCQQ22RPN0xGNo9yiIUEAVE+uTRFNKGEHMIvJzgB291FJQtG/WoN/Sy/DeoZkUYndA7CbfFgiz9sq3GqJwyqTM+Ikzw6VCQ3TVWYO/6+yaEzT1NZ/rw00YhDyFoH+yGTkX5lrSUo8lGf3T9OODTdSS1203xtj4dhGbY70sPGMxDfucnO6piVcuRfh9bWg1VX+MTjqpBQE1J0DKJanhoh6lKbPOJhRbi0TIoCz1a3btbbKLDq2YJl7FXA3QNdBsR4qVJ3xmgWlH/eTUskCE2YxDzHmkxgZN2mL22SfeUJYtDRswRG0UGc6pvg2xrR0iEDB0UbH4FQK46fWv8aiOejteLlAAyA9HNA8yi73rTEjq55wpO19/MYj+6oUGEHpFaJje77INTHAKpdbfkp8iKotNXFYLM2hsjyau+x/AFjg87kEdIUVVHHLVMDhOq6NO+foM0nFOIv4lr2Yjl15+ImMSTEfcBr+nWaomnb9G8i5ptqz9bMHbxAcHpzWUBH9SZhzrCWZNDzOm2X2K6mpXhg4WX7VJMoIJk/f+bxTTKnWBawdnpCdbdG5GYQh7usqjPuELFYRTsx+6Tqoddp4KIEyMMxO81XObiN5vEBg74ygunxrjKNg5FoOkUA79YvcGBVVU1FNRl3d8IslXFqhEOT35nxGBB0T7ZORii34tZ2E551NkIo1WbCwilD3Dlgw==
ui-auth-token: AgDStP4jHhMehx/SAv5x2aQrJwChnWq8WhV/LVTSuuCSSy9kFQGa3Izyl21sMhLKUgrlS030GH50lbku+IY90S+MSBfaCq0Xb8qwohfH+qJulMRgljoU8r58/3ZsaBzbGJjiNMgBCwGlvuK85t3R3662ftwikahWqLfwAahi12L0llUDNyG9UB0Os3oR2CVAt83+YB07YspCWRzwuOz9D1SgOL/g/JF2hoL43pDD65BqYKdEuLFInJZx1Ul68V/FPmJK1gmJb/Kv8pgtk0sGTzxbbTdIQ1Ugf6+8//CWEq6GsMeEZG9VwExL/KYqobnbqd02FRgSSvWybhWLjiALLQvAFtce6FR6bur0rsariyogGjYCuXYdRnhf7QnB6NopwplhFP37Qf6E4q2pJTJ6Fzv0xq+XlfzNkn9Jvebrdkk/5CThW+wPAfx8r1VIySqBsYnPO/ZQDkfQ5ocX6fmzZ1iCg+0NI24k2YwCeMZtEAGpKEtejS0BLvqrilUG6Oz9sHCGHro4Bv5B/jzUjGbye0DSDj1f6c2RcpqMiUxfPvydJIcJGhrTx8sZS3qhEWME7kwjJCZpHEzfdv0weSJbgVGBSh9e1wjZxxeJGXuZKdFzdhpNEWP4uScGW0UnRDwxZzHMsLjS/W30mQmwmgJVTKdPl6VQrvdq7m4AC6+/d7iXlHazieC1KpBme4hWzO+/h7qRw1P5Va/ZWZtnGs8476J8hIojRtOJ/CdWwVLUa0gHo4CYnempIMwCIS3GtA==
template:
metadata:
creationTimestamp: null
name: dot-ai-secrets
namespace: dot-ai
+70
View File
@@ -0,0 +1,70 @@
---
# Namespace must be created first (sync-wave: -1)
apiVersion: v1
kind: Namespace
metadata:
name: dot-ai
annotations:
argocd.argoproj.io/sync-wave: "-1"
---
# ArgoCD Application syncs last (sync-wave: 1)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dot-ai-stack
namespace: argocd
annotations:
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:
app.kubernetes.io/name: dot-ai-stack
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ghcr.io/vfarcic/dot-ai-stack/charts
chart: dot-ai-stack
targetRevision: "0.56.0"
helm:
releaseName: dot-ai-stack
valueFiles:
- $values/infra/values/base/dot-ai-stack-values.yaml
- $values/infra/values/upc-dev/dot-ai-stack-values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: dot-ai
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- Validate=true
- ServerSideApply=true
- Replace=false
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dot-ai-stack.yaml
- dot-ai-secrets.yaml
@@ -0,0 +1,16 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: app-credentials
namespace: mcp10x
spec:
encryptedData:
FLOWCASE_TOKEN: AgAjQ0Jm779LiB2/EBKtIl18Iky/tgl7VvEvkJfr82mbe7JIp99+I8WPIMgmlXsxWBDfwJ/DZoDEAKG5o8mvwc7DtaE7LkpPSXKRIviLrm+7amNdEpwb1zKjCmZ3vNOPXLnCTD3W1od8u106MEvrZpt1N1Pzav/QUElAA4lqyf4AZPuio0H/7jF8QciVHIN4BdofodOmWWCTxP1aVMylat3txlkzuqwkXc1fKBPmOKC1qWepAG7dlQ3xJ2C5Cinz5IqOYnQPO6sm/Gp+c8yQSS+0NpKO3dp93yuwRsOLOMNpCeeYOyzn2ypFIdiBuLA5k85l5WyI9BCvRjP+kzRX+u69XL7p2eMEg+vRVfoOWNetysYCdK+96bWNVVleQEuPAFxsbJWK5eV8jTZk+QeFES8kkXQwUoTMqh+JULgONakxrzc1LmA+FtKNwHWCHalh8pLQ9KeI2i9E5CtotjHfEGLE6T0ibwwOg1ZrL3irQVII8VFm4Rk+dSicbeEMAtIaII023NWuZlonrwIIy+GSVzskhhcjV7t51hylqFA3yfb+RQCzdGYA5fySlJBWhli8IotvqnDmWduPBHGb/+yrdXHsaiDqWuUu6JBUg4JqM+AMa8FkaFh8SbctbGBoycpzLVFn9N2qWviojhC9s1NJ4MiCegvE6IsCLaHidY4UXeeGjUK6Mx6h4x3y/q7ZIrBIpUOJ+8I4Mgvi9sTCogeR+vWtpP9ZZ/31MXELuu/ho3Wn9Q==
template:
metadata:
creationTimestamp: null
name: app-credentials
namespace: mcp10x
type: Opaque
+5
View File
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- mcp10x.yaml
- forte10x-app-credentials-sealed.yaml
+39
View File
@@ -0,0 +1,39 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mcp10x
namespace: argocd
annotations:
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:
app.kubernetes.io/name: mcp10x
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/mcp10x/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: mcp10x
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+5
View File
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- musicman.yaml
- musicman-credentials.yaml
@@ -0,0 +1,19 @@
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: musicman-credentials
namespace: music-man
annotations:
argocd.argoproj.io/sync-wave: "12"
spec:
encryptedData:
DATABASE_URL: AgBGLu8Rw9z9WMo3uX7fezN7tOVlEsmWtikFlyBxuSuQ1dCv6KTCePkwxJx4LuKvaHXlwdWl5yP8wQxMJP0BNJ1wewFb9zeUkP1YuCz4MrfuXq1zrecIr86R5hNbPiOb66e/4oOTCY/z3QREX9WjZdLJV/PCyBz8MP0D51pgWXpM6CBdhwpFbHSALyJk89+q44c9KkRxAUG2OLnesMeRe9nXJt5ariUCl9Qd2POIjx2hSNII1l0KbTcjI9hCf91DYM6poqKYYQUpnrjKv3LJwWS79I2b56+iTtroH3usIRgaiwgtFt2INm+8gwLBmC4xxKJ5VAjjYB/3dcN9XeboXvj0NB05P9jS3e77imUFANIB9coeaNlcvRWxwGCewYMp8+7RT7jPVA41/+aT/zT74tq9WhkKvgrr1It9/5fRnXtFEkhZg5bBcYCChzooarHkiwKlA3Wo0CrFsDPqy89oZrnwMRnVqKWBf79koZV4l7uCA0do9ojf55lTy8mt3mKQkwfqK9UdzZNbYzH0/Fk6gxlSxANOOqe7kt6VPywYUBnh6JS5U+kdTgNeSrFy/xqLFz28fXuikSJvLEouSFu66MeT+6uvYEmdfdLeh7quW/n+p7QTok3v3kRYJ/1Dl8ZtgvM7e8F/J5bLcacj394AJ/bBt+RIDa+XBjNNPrWKcWt/mkudZ25F/84G+hNxYQv7PIbhYfA1JTuHmQSoF+xah5QhKpyNpI3+knJmJj/4MhPKLnTuebg0xfbPevm2CU9fSa4sPIqmSvSGtqlXODvCfDSFEYzWfyfXV5Tys1NGAt04V8fl9A9UxULUm510NCeD0jzFeeYm3ZJiyavA5xF6hXCHoqLE
JWT_SECRET: AgAJmQ+WPiuifW8jCMOkiZNYj7xb29Z4woRO2ir6bE/qX7y4LkR6SjmsS/FSJqV7apiKgdrQ0TTxSU/7JlwzBkAgzH5UgcwQmQ8imC7JDYLmHssAeZz75Ft/TwCo7ToCPLr4YBetwz39XYyTAi7wgDzKNX8vYuEEaxP5xOTniyIysMzLVGs0EJSB7398fTgQo0t7rIuBBIP072iX93YfLvvTE7rrqg6uIE4D7LJ3oqvKJlN0MnpDPbdqWvxO7lX6P45ACCveCW1G0U77HOLj4QwyORsIEEUkOqDtr1CF7EQJLEZaREvpB42WI6sQEqsORr7F/Z0d0DdCjCSjKolkb/3z9uDUGwGkuTLJvOzMWAQFtQi1pjImCo79jNoKC0h0iBWqXBQbvg5dkCLVDD4A0lq7GD3KXtjktg9hbqPqFgAyYcJ6cUYp7xx53Y6yYyUS2T9KljvqtBR3bZ7yvdLMvsZbecTOgBpTvoELWa+bsympoZYu9PWCE3YnDyYmLLiWRnKgZuA6LhrNZzxzd6ytiasXn8ZIqrec4Rsj2avtmc7NQPwSe9/MmfUG7pWL1T3D9k4vqt4lfoFTswgVIkFE2h/DQDrEhnEAsLDOSYR8LCdvyQWbiSy3d2VvDOHdogyFbhvoHEyH1+cLMR5zgGgDbTyPidUxjkq//A8l1d0twFaJ0y1rvo/RJ/KnXl1E9OdV42i1jZCsCl81qZjfGaIQ18tNB/bwbqsWZRJxxEuLIKJHX2FuKXZDfsj+E0Qsmhj6kwGpVsTkqgLO7mOCCk5d8Lft
POSTGRES_PASSWORD: AgAG9l5ZbBHtT9o+ysPIy9TAEwVH17xWlKIDScvCY7iVM1gKuZav6nye84DVCNccgZ8nCtu/GQay35ejttR9CLu9Du89OhkFJMGKXSyij7P1+TJP/BPD2+XIzzcMJimq8jrObzqLTPdgfrTs21nJ50GaP0qEvXgEAh+aWyspHrwMnyIJMWq8jpl2ITIEsUpvbxD9zx0yJ7rdkpjrW8Q6AA5bzL9qCmgz8M06+wR86Zu+u+6qOV2SCGDIMmTPqQdvQOU8rzVYF7Xrl4iASju9bdlhjuWSzzYJmDg7O7Xwam4JEUwpHK4zjEaPydF55Lc6t8Sf3P00VMRzBrwZxUeVWCAvzSiiQnfe6/kbG8neCJ99kJoJqULsZ9dvsGrDyz5wu1QSVBHhUseeX8IMoH3R97XJFtQ32EenMTm/SmnmXnI2LD06TB7IBhyTK2uBx2QtYRLasKa+bb0mCD8cJEtpBo9/Y5VY6ILoGH7jEH5WRO8XXLoc6z2GmfxyjOe2cu5C+PUg4cfbafevFiEXp8TbCgiV2214VZ6yMoDNM7xabSPpyo0N4oZ0obiy4Ltc0gwwDDXpEAmfiIjKQ8mu9pywU5OpWhw0bvGsuEeI5nQVWbqvziwdfrUEfBhq+X8hUtNQfrC0DNAePkQVJDbhfXyknWpo6M6upCE2EYeJrM7kJiVV7FkamQcTxYwhl+XrdV4nBwSUolQ/Jy7Jq5Y00iQnm57O49OpRkZWhwBRSWkaF5pbTIPWG/JDii7ixb60sagFTAF1g+IfPPnNjwvuqqEIEyy3
template:
metadata:
creationTimestamp: null
name: musicman-credentials
namespace: music-man
type: Opaque
+52
View File
@@ -0,0 +1,52 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: music-man
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "11"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels:
app.kubernetes.io/name: music-man
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/musicman/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: music-man
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
+5
View File
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ts-mcp.yaml
- ts-mcp-secrets-sealed.yaml
@@ -0,0 +1,13 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: ts-mcp-secrets
namespace: ts-mcp
spec:
encryptedData:
AZURE_CLIENT_SECRET: AgCWj525+NHkZ8XG97hEe4RS0SDC0QIGDXmEvzSlIqJQ9XVZEeKxVuAYmJ+w/HH7zBXD3qlZISeOPKn3FbMEeRukmYK0d5PsH26tRUMPoMzwWCuQkZIQ83uX9Pz/wMiqW8aZFIxpdEiUgVdanxHSFoDRPC1VlSEtV9B9yN2MgXBID5s0oje5BM9ttc4WVRe6+9pMeaOC6u+YUgcfY7xPLetZfC9nQO4zn4jYhoQXfAddwMzNODvQNGPzIv6PXDXJweTwdmtGaxM6eDdcCJI/30bEV9prA5m6UlgTZ/Qp+onU70KdkBA9gM9tMMVUR6j/2sbWzqMP/rVaFLeUH1PjHv15n4EieWyuDyYEfmZNDFXc7O9RIK6P0jCIE+t3myxK2ZQ7cfXprdOSj94au0qP6leat0UUVoc9CFJHHtrNxXYWl7IYVhwvIQCMSgO2qoAXkdW4wKVJAcbJadJjoL2pWxzjaD4GgnUaAxWBANqZI2lD8CED4VfUVMB0ZUYRS/zvy/eqIGlT8WbzwTYFi3YDZRvAUIknxaWEavIG4x52d0FqTmFYY06W53fGYfBrUjJI54GWYyBpKdZTf7b/AlAN0+kwkk6OqsUWwWDqxR7LVCcPhjSIKd/THp+Tbq9z5TiPIHxOO9V60u51f8IoQrEgQfNov7CEGQZ8B9HUGObjNc5MhujzBJasMhrUcd2Ddk6KWk07B7223p/gIEM+81ZWQYUcc29+U/j1dQyRNZy/TC56ywe5DDBJSoGp
template:
metadata:
name: ts-mcp-secrets
namespace: ts-mcp
+40
View File
@@ -0,0 +1,40 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: ts-mcp
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "11"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels:
app.kubernetes.io/name: ts-mcp
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/ts-mcp/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: ts-mcp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+4
View File
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base/musicman
@@ -0,0 +1,47 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dbunk-demo
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "12"
labels:
app.kubernetes.io/name: dbunk-demo
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/dbunk-demo/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: dbunk-demo
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
@@ -0,0 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dbunk-demo.yaml
@@ -0,0 +1,37 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: forte-drop-mcp
namespace: argocd
annotations:
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:
app.kubernetes.io/name: forte-drop-mcp
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/forte-drop-mcp/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: forte-drop
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- forte-drop-mcp.yaml
# No keycloak-client config + no auth-oidc Secret for mcp mode. The chart's
# auth.type: mcp auto-registers the MCP client; the sidecar is an RFC 9728
# resource server that validates tokens (no client-secret of its own).
# forte-drop-secrets (shared with web) covers PG + S3 creds.

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