Ga naar inhoud

Architecture Map

One-screen orientation for a dev or agent: which repos exist, how they connect, and how GitOps, secrets and auth flow through the cluster. The deep runbooks live in the other files in this folder — this is the map, not the territory. Last verified 2026-06-23 against cluster garden-wh2mnkj--con-prod-external (con-prod).


1. The repos — who owns what

This platform spans four repos. None is self-contained; a change often touches two.

Repo Role Argo reads from
Nextcloud-base (this repo) The multi-tenant Nextcloud platform: tenant values, charts, platform components, and the ESO consumers Codeberg conduction/Nextcloud-base (main)
cluster-infra Cluster-wide capabilities — external-dns, External Secrets Operator, storage Codeberg Conduction/cluster-infra (main)
KeyCloak Identity — realm commonground at iam.commonground.nu Codeberg Conduction/KeyCloak (main)
openwoo-app-config The provisioning control-plane (platform.commonground.nu) that opens tenant PRs Codeberg Conduction/openwoo-app-config

Golden rule: Argo reads Codeberg, never GitHub. GitHub remotes exist as mirrors (the org was shadowbanned) but Argo ignores them. git push origin … on most of these repos pushes to the GitHub mirror and does not deploy — push to the codeberg remote.


2. GitOps — how code reaches the cluster

  dev / agent          Codeberg (per repo)        Argo CD (ns argocd)            cluster
  ───────────          ───────────────────        ───────────────────            ───────
  git push ──────────► main ──────────────────────► Application / AppSet ────────► workloads
                       GitHub mirror (ignored) ✗     │
                                                      ├─ AppProject gates which
                                                      │   sourceRepos / destinations /
                                                      │   resource-kinds are allowed
                                                      └─ apps registered MANUALLY
                                                         (kubectl apply) for cluster-infra;
                                                         tenants via the nextcloud-tenants
                                                         ApplicationSet (goTemplate)
  • AppProjects are the guardrail. If an app's source repo, destination namespace, or a resource kind is not whitelisted in its AppProject, the sync fails with InvalidSpecError / SyncFailed: resource ... not permitted. Widening the project is usually the missing step when wiring a new capability.
  • Tenant apps are generated by the nextcloud-tenants ApplicationSet from a glob over values/tenants/tenant-*.yaml. The Argo Application is named nc-<tenant>; the namespace is the bare tenant name (e.g. app nc-straatje-accept → ns straatje-accept).
  • accept-environment tenants run on the same con-prod cluster as prod tenants.

3. Secrets — External Secrets Operator (ESO)

  cluster-infra                     Nextcloud-base (consumers)            tenant namespace
  ─────────────                     ──────────────────────────           ────────────────
  ESO operator + CRDs  ──provides─► ClusterSecretStore                 ┐
  (ns external-secrets,             "nextcloud-shared-store"           │
   Helm chart 2.6.0)                (kubernetes provider) ─reads──┐    │
                                                                  ▼    ├─► ExternalSecret ─► nextcloud-secrets
                                    seed Secret (out-of-band):         │   charts/tenant-secret    (assembled
                                    nextcloud-platform/                │   (managed tenants only)   Secret)
                                    nextcloud-s3-seed                  │
                                    ClusterGenerator "nextcloud-       │
                                    password" (random pw) ────────────┘
  • ESO is a cluster-wide capability, not Nextcloud-specific — that is why the operator lives in cluster-infra. Any app/namespace may consume it; only the Nextcloud consumers are wired today. Another team must add its own ExternalSecret + store/generator and widen its own AppProject to permit the external-secrets.io kinds.
  • The shared Fuga S3 seed (nextcloud-platform/nextcloud-s3-seed) is created out-of-band by copying s3-access-key/s3-secret-key from any existing tenant's nextcloud-secrets (they are platform-identical) — never in Git.
  • A tenant's nextcloud-secrets is ESO-rendered only when the tenant file sets tenant.secrets.managed: true (new/web-created tenants). Existing tenants keep their script-applied secrets (scripts/create-tenant-secret.sh) untouched. See SECRETS.md.
  • ESO uses external-secrets.io/v1 (the 2.x major dropped the served v1beta1); ClusterGenerator is generators.external-secrets.io/v1alpha1.
  • Naming note: "External" reflects ESO's origin (syncing from external vaults like Vault/AWS). Here we use its internal features — the kubernetes provider (source is an in-cluster Secret) and generators (values made in-cluster). The same consumer shape would work later if a real external vault is added, which is why we standardised on it.

4. Auth / SSO (control-plane)

  browser ─► oauth2-proxy ─► Keycloak (realm commonground, ─► Google (brokered IdP)
             (openwoo-       iam.commonground.nu)
              platform)      client: openwoo-provisioner
   /logout ─► /oauth2/sign_out?rd=<Keycloak end-session> ─► back to platform.commonground.nu/

This is the openwoo-app-config provisioning control-plane, not a tenant concern, but it shares the same Keycloak. Logout needs the Keycloak client to list https://platform.commonground.nu/ as a Valid post logout redirect URI.


5. Why this storage architecture (node-upgrade resilience)

During Kubernetes node upgrades the provider blocks the OpenStack API, which breaks CSI attach/mount and any in-cluster NFS provisioner (CreateContainerConfigError, mount failures, outages). This platform removes the critical dependency on in-cluster NFS and fragile CSI flows:

Concern Fragile (traditional) This platform (resilient)
User files NFS / block storage S3 primary (Ceph RGW)
Config RWX NFS volume ConfigMaps + Secrets (stateless)
custom_apps RWX NFS volume baked into the image (immutable)
appdata RWX NFS volume minimal CephFS (provider-managed)
Sessions / locking local / file-based Redis (shared, distributed)

Result: during a node upgrade pods reschedule without waiting on CSI; user data in S3 stays available.


6. Known issues / watch-outs (2026-06-23)

  • Canary override in the nextcloud-tenants ApplicationSet is selected via a templated filename plus helm.ignoreMissingValueFiles: true: canary tenants load canary-overrides.yaml, non-canary tenants resolve the name to a missing file that is skipped. This replaced an earlier {{- if }} list-control line that was invalid YAML and made kubectl apply fail (~line 62); the AppSet is appliable again the documented way.
  • Tenant deletion does NOT auto-remove the namespace. The ApplicationSet sets preserveResourcesOnDeletion: true; manual cleanup is required. See REMOVING-TENANT.md. (If any doc says Argo auto-deletes the namespace, it is wrong.)
  • Chart version lives in the ApplicationSet (targetRevision, default 8.9.0) and per-tenant via tenant.chartVersionnot in values/common.yaml.

7. For agents: start here

  1. Read this map, then the topic doc you need (see README.md in this folder for the index).
  2. Argo reads Codeberg — a GitHub push will not deploy.
  3. AppProjects gate everything — a sync failure is often a missing whitelist entry.
  4. Namespace = bare tenant name (straatje-accept), the Argo app is nc-<tenant>.
  5. prod paths (*-prod, clusters/prod/…) and pushes to main go through the operator, not blindly — confirm first.
  6. Verify live state read-only (kubectl get app -n argocd …) before asserting; this platform spans four repos plus a cluster and assumptions bite.