diff --git a/DEV-349-progress.md b/DEV-349-progress.md new file mode 100644 index 0000000..1a2d900 --- /dev/null +++ b/DEV-349-progress.md @@ -0,0 +1,48 @@ +## Dozzle Deployment Progress + +### Completed + +1. ✅ Created deployment manifests in `apps/dozzle/` directory: + - Namespace, ServiceAccount with cluster-wide RBAC for pod log access + - PVC (1Gi, hcloud-volumes-encrypted, ReadWriteOnce with Recreate strategy) + - Deployment with oauth2-proxy sidecar for Pocket ID OIDC authentication + - Service routing to oauth2-proxy (port 4180) + - Ingress with TLS for `dozzle.basicstack.de` + +2. ✅ Created ArgoCD application (`app-dozzle.yaml`) with automated sync + +3. ✅ Created Pocket ID OIDC client: + - Client ID: `179c13f2-d251-4e1e-b1a0-c070df350c4e` + - Callback URL: `https://dozzle.basicstack.de/oauth2/callback` + - Sealed secret with client credentials and oauth2-proxy cookie secret + +4. ✅ Configured oauth2-proxy sidecar (running successfully) + +5. ✅ All manifests committed and pushed to repository + +### Current Blocker + +Dozzle container is failing to start with error: **"Could not connect to any Docker Engine"** + +**Root cause**: Dozzle v10.6.10 is not auto-detecting Kubernetes mode despite: +- Proper RBAC permissions (ClusterRole with pods, pods/log, namespaces access) +- Service account correctly mounted at `/var/run/secrets/kubernetes.io/serviceaccount/` +- ClusterRoleBinding connecting service account to ClusterRole + +**Troubleshooting attempted**: +- Tried various command-line flags (`--agent`, `--no-analytics`) +- Verified RBAC permissions are correct +- Checked service account token is mounted +- oauth2-proxy sidecar is working correctly (1/2 containers ready) + +**Next steps**: +1. Research Dozzle Kubernetes deployment examples/Helm charts for missing configuration +2. Consider alternative: Try older Dozzle version or different container log viewer +3. May need to explicitly configure Kubernetes API endpoint or use different deployment approach + +**Current pod status**: +``` +dozzle-f4d557466-qh28j 1/2 CrashLoopBackOff +``` + +All infrastructure is in place; just need to resolve Dozzle's Kubernetes detection issue. diff --git a/apps/app-argocd.yaml b/apps/app-argocd.yaml index 0b68c31..565fef8 100644 --- a/apps/app-argocd.yaml +++ b/apps/app-argocd.yaml @@ -3,16 +3,27 @@ kind: Application metadata: name: argocd namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io spec: project: default - source: - repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git - targetRevision: main - path: apps/argocd destination: server: https://kubernetes.default.svc namespace: argocd + sources: + - repoURL: https://argoproj.github.io/argo-helm + chart: argo-cd + targetRevision: 10.4.0 + helm: + releaseName: argocd + valueFiles: + - $values/apps/argocd/values.yaml + - repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/argocd + ref: values syncPolicy: syncOptions: - - CreateNamespace=true - - ServerSideApply=true + - CreateNamespace=true + - ServerSideApply=true + - ApplyOutOfSyncOnly=true diff --git a/apps/app-basicstack-org.yaml b/apps/app-basicstack-org.yaml index a9aecb2..8499266 100644 --- a/apps/app-basicstack-org.yaml +++ b/apps/app-basicstack-org.yaml @@ -6,9 +6,9 @@ metadata: spec: project: default source: - repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/basicstack.org.git + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git targetRevision: main - path: k8s + path: apps/basicstack-org destination: server: https://kubernetes.default.svc namespace: basicstack-web diff --git a/apps/app-dozzle.yaml b/apps/app-dozzle.yaml new file mode 100644 index 0000000..8bad403 --- /dev/null +++ b/apps/app-dozzle.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: dozzle + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/dozzle + destination: + server: https://kubernetes.default.svc + namespace: dozzle + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/app-forgejo-runner.yaml b/apps/app-forgejo-runner.yaml new file mode 100644 index 0000000..65e6259 --- /dev/null +++ b/apps/app-forgejo-runner.yaml @@ -0,0 +1,18 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: forgejo-runner + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/forgejo-runner + destination: + server: https://kubernetes.default.svc + namespace: forgejo + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/apps/app-backup.yaml b/apps/app-grist.yaml similarity index 75% rename from apps/app-backup.yaml rename to apps/app-grist.yaml index f7e004b..3f7e6f0 100644 --- a/apps/app-backup.yaml +++ b/apps/app-grist.yaml @@ -1,17 +1,20 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: backup + name: grist namespace: argocd spec: project: default source: repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git targetRevision: main - path: apps/backup + path: apps/grist destination: server: https://kubernetes.default.svc - namespace: backup + namespace: grist syncPolicy: + automated: + prune: true + selfHeal: true syncOptions: - CreateNamespace=true diff --git a/apps/app-harbor.yaml b/apps/app-harbor.yaml new file mode 100644 index 0000000..5af9f41 --- /dev/null +++ b/apps/app-harbor.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: harbor + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/harbor + destination: + server: https://kubernetes.default.svc + namespace: harbor + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/app-platform-prod.yaml b/apps/app-kaneo.yaml similarity index 75% rename from apps/app-platform-prod.yaml rename to apps/app-kaneo.yaml index feaef59..407252f 100644 --- a/apps/app-platform-prod.yaml +++ b/apps/app-kaneo.yaml @@ -1,17 +1,20 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: platform-prod + name: kaneo namespace: argocd spec: project: default source: repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git targetRevision: main - path: apps/platform-prod + path: apps/kaneo destination: server: https://kubernetes.default.svc - namespace: platform-prod + namespace: kaneo syncPolicy: + automated: + prune: true + selfHeal: true syncOptions: - CreateNamespace=true diff --git a/apps/app-openwebui.yaml b/apps/app-openwebui.yaml new file mode 100644 index 0000000..edbd89c --- /dev/null +++ b/apps/app-openwebui.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: openwebui + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/openwebui + destination: + server: https://kubernetes.default.svc + namespace: openwebui + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/app-outline.yaml b/apps/app-outline.yaml new file mode 100644 index 0000000..08a2e18 --- /dev/null +++ b/apps/app-outline.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: outline + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/outline + destination: + server: https://kubernetes.default.svc + namespace: outline + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/app-pangolin.yaml b/apps/app-pangolin.yaml new file mode 100644 index 0000000..8eefe97 --- /dev/null +++ b/apps/app-pangolin.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: pangolin + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/pangolin + destination: + server: https://kubernetes.default.svc + namespace: pangolin + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/app-velero-monitoring.yaml b/apps/app-velero-monitoring.yaml new file mode 100644 index 0000000..b3d3835 --- /dev/null +++ b/apps/app-velero-monitoring.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: velero-monitoring + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: velero + directory: + recurse: false + include: '{servicemonitor.yaml,prometheusrule.yaml}' + destination: + server: https://kubernetes.default.svc + namespace: velero + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - ServerSideApply=true diff --git a/apps/app-velero-restore-drill.yaml b/apps/app-velero-restore-drill.yaml new file mode 100644 index 0000000..87b782d --- /dev/null +++ b/apps/app-velero-restore-drill.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: velero-restore-drill + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: velero/restore-drill + directory: + recurse: false + include: '*.yaml' + destination: + server: https://kubernetes.default.svc + namespace: velero + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - ServerSideApply=true diff --git a/apps/app-velero-schedules.yaml b/apps/app-velero-schedules.yaml new file mode 100644 index 0000000..dce1c07 --- /dev/null +++ b/apps/app-velero-schedules.yaml @@ -0,0 +1,23 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: velero-schedules + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: velero/schedules + directory: + recurse: false + include: '*.yaml' + destination: + server: https://kubernetes.default.svc + namespace: velero + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - ServerSideApply=true diff --git a/apps/app-velero.yaml b/apps/app-velero.yaml new file mode 100644 index 0000000..c85adea --- /dev/null +++ b/apps/app-velero.yaml @@ -0,0 +1,21 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: velero + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/velero + destination: + server: https://kubernetes.default.svc + namespace: velero + syncPolicy: + automated: + prune: false + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/apps/app-vikunja.yaml b/apps/app-vikunja.yaml new file mode 100644 index 0000000..558ef1e --- /dev/null +++ b/apps/app-vikunja.yaml @@ -0,0 +1,20 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: vikunja + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/vikunja + destination: + server: https://kubernetes.default.svc + namespace: vikunja + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/argocd/README.md b/apps/argocd/README.md index 5b5a003..dad6d9d 100644 --- a/apps/argocd/README.md +++ b/apps/argocd/README.md @@ -2,69 +2,83 @@ This directory contains the Argo CD deployment configuration for the basicstack.de k3s cluster. +Argo CD itself is installed from the community Helm chart (`argoproj/argo-helm`, chart `argo-cd`). This directory holds the chart values file plus a small kustomize wrapper for the ingress and sealed secrets that stay in git. + ## Files -- `argocd-install.yaml` - Auto-generated Argo CD installation manifest (DO NOT EDIT DIRECTLY) -- `kustomization.yaml` - Kustomize overlay that adds resource limits and other customizations -- `argocd-ingress.yaml` - Ingress configuration for Argo CD UI -- `argocd-oidc-secret-sealed.yaml` - Sealed secret for OIDC integration -- `repo-*.yaml` - Sealed secrets for Git repository access +- `values.yaml` - Helm values for the `argo-cd` chart (image tag, OIDC/RBAC config, resource limits, ingress disabled). +- `kustomization.yaml` - Kustomize wrapper for the ingress + sealed secrets (does NOT install Argo CD itself). +- `argocd-ingress.yaml` - Ingress configuration for the Argo CD UI (Traefik + cert-manager `letsencrypt-prod`). +- `argocd-oidc-secret-sealed.yaml` - Sealed secret for Pocket ID OIDC integration. +- `repo-*.yaml` - Sealed secrets for Git repository access. -## Resource Limits +## How the install is wired -**IMPORTANT**: Resource limits were added after DEV-281 (resource exhaustion incident on 2026-07-12). +The root [`../app-argocd.yaml`](../app-argocd.yaml) is an Argo CD `Application` with two sources: -All Argo CD components now have memory limits to prevent OOM incidents: +1. The public Helm chart at `https://argoproj.github.io/argo-helm`, chart `argo-cd`, `targetRevision` pinned in git. +2. This repo (`ref: values`) providing the `values.yaml` used by source (1) AND the ingress/sealed secrets applied via `kustomize`. + +Once bootstrapped, Argo CD manages its own install by syncing this Application. + +## Resource limits + +Memory limits were added after DEV-281 (resource exhaustion incident on 2026-07-12) and are now driven by `values.yaml`: | Component | Memory Limit | Memory Request | |-----------|--------------|----------------| -| application-controller | 512Mi | 256Mi | +| application-controller | 1Gi | 512Mi | | repo-server | 512Mi | 256Mi | | redis | 256Mi | 128Mi | | server | 256Mi | 128Mi | | notifications-controller | 128Mi | 64Mi | | applicationset-controller | 256Mi | 128Mi | -These limits are based on observed usage patterns and provide headroom while preventing unlimited memory consumption. - ## Deployment -### Option 1: Apply with kustomize (RECOMMENDED) +### Steady state (managed by Argo CD) + +Once the cluster is bootstrapped, changes to this directory are picked up by the root `argocd` Application on the next sync. No manual `kubectl apply` is required. + +### First-time / disaster-recovery bootstrap + +Argo CD cannot install itself while it is gone. Bootstrap with helm, then hand ownership back: ```bash -kubectl apply -k apps/argocd/ +helm repo add argo https://argoproj.github.io/argo-helm +helm repo update +helm install argocd argo/argo-cd \ + --version 10.4.0 \ + --namespace argocd --create-namespace \ + --values apps/argocd/values.yaml \ + --wait --timeout 10m + +kubectl apply -k apps/argocd/ # ingress + sealed secrets +kubectl apply -f apps/app-argocd.yaml # hand ownership back to GitOps ``` -This will apply the base manifests plus all patches defined in `kustomization.yaml`. - -### Option 2: Direct apply (not recommended) - -```bash -kubectl apply -f apps/argocd/argocd-install.yaml -kubectl apply -f apps/argocd/argocd-ingress.yaml -# etc. -``` - -**Note**: This skips the resource limit patches and is NOT recommended. - ## Updating Argo CD -When updating to a new Argo CD version: +Bump the chart and the image tag in a single PR: -1. Download the new install manifest: +1. Refresh the local helm repo cache and check what's available: ```bash - curl -sSL https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml > argocd-install.yaml + helm repo update + helm search repo argo/argo-cd --versions | head ``` -2. Apply with kustomize (resource limits will be automatically applied): +2. Bump both fields together, keeping them in sync with the chart's `appVersion`: + - `apps/app-argocd.yaml` -> `spec.sources[0].targetRevision` (chart version, e.g. `10.4.0`) + - `apps/argocd/values.yaml` -> `global.image.tag` (app version, e.g. `v3.5.1`) + +3. Optionally render locally to sanity-check the output before opening the PR: ```bash - kubectl apply -k apps/argocd/ + helm template argocd argo/argo-cd \ + --version \ + -f apps/argocd/values.yaml -n argocd | less ``` -3. Verify resource limits are in place: - ```bash - kubectl get statefulset,deployment -n argocd -o custom-columns='NAME:.metadata.name,MEMORY_LIMIT:.spec.template.spec.containers[0].resources.limits.memory' - ``` +4. Open the PR. After merge, Argo CD syncs itself onto the new version. ## Troubleshooting @@ -74,22 +88,21 @@ When updating to a new Argo CD version: kubectl top pods -n argocd ``` -### Check if resource limits are applied +### Check applied resource limits ```bash -kubectl get deployment,statefulset -n argocd -o json | jq '.items[] | {name: .metadata.name, limits: .spec.template.spec.containers[0].resources.limits}' +kubectl get deployment,statefulset -n argocd -o custom-columns='NAME:.metadata.name,MEMORY_LIMIT:.spec.template.spec.containers[0].resources.limits.memory' ``` -### Rollback if needed - -If there are issues after applying resource limits: +### Rollback if a chart upgrade misbehaves ```bash -# Remove limits from a specific component -kubectl patch deployment -n argocd argocd-server --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/resources"}]' +helm -n argocd history argocd +helm -n argocd rollback argocd ``` ## History -- **2026-07-12**: Added resource limits via kustomization to prevent OOM incidents (DEV-281) -- **2026-07-11**: Initial deployment +- **2026-08-23**: Switched to the community Helm chart, bumped to `v3.5.1` / chart `10.4.0` (DEV-519). +- **2026-07-12**: Added resource limits via kustomization to prevent OOM incidents (DEV-281). +- **2026-07-11**: Initial deployment (vendored `install.yaml`). diff --git a/apps/argocd/argocd-install.yaml b/apps/argocd/argocd-install.yaml deleted file mode 100644 index dd2a5cf..0000000 --- a/apps/argocd/argocd-install.yaml +++ /dev/null @@ -1,33364 +0,0 @@ -# This is an auto-generated file. DO NOT EDIT -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/name: applications.argoproj.io - app.kubernetes.io/part-of: argocd - name: applications.argoproj.io -spec: - group: argoproj.io - names: - kind: Application - listKind: ApplicationList - plural: applications - shortNames: - - app - - apps - singular: application - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.sync.status - name: Sync Status - type: string - - jsonPath: .status.health.status - name: Health Status - type: string - - jsonPath: .status.sync.revision - name: Revision - priority: 10 - type: string - - jsonPath: .spec.project - name: Project - priority: 10 - type: string - name: v1alpha1 - schema: - openAPIV3Schema: - description: Application is a definition of Application resource. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - operation: - description: Operation contains information about a requested or running - operation - properties: - info: - description: Info is a list of informational items for this operation - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - initiatedBy: - description: InitiatedBy contains information about who initiated - the operations - properties: - automated: - description: Automated is set to true if operation was initiated - automatically by the application controller. - type: boolean - username: - description: Username contains the name of a user who started - operation - type: string - type: object - retry: - description: Retry controls the strategy to apply if a sync fails - properties: - backoff: - description: Backoff controls how to backoff on subsequent retries - of failed syncs - properties: - duration: - description: Duration is the amount to back off. Default unit - is seconds, but could also be a duration (e.g. "2m", "1h") - type: string - factor: - description: Factor is a factor to multiply the base duration - after each failed retry - format: int64 - type: integer - maxDuration: - description: MaxDuration is the maximum amount of time allowed - for the backoff strategy - type: string - type: object - limit: - description: Limit is the maximum number of attempts for retrying - a failed sync. If set to 0, no retries will be performed. - format: int64 - type: integer - refresh: - description: 'Refresh indicates if the latest revision should - be used on retry instead of the initial one (default: false)' - type: boolean - type: object - sync: - description: Sync contains parameters for the operation - properties: - autoHealAttemptsCount: - description: SelfHealAttemptsCount contains the number of auto-heal - attempts - format: int64 - type: integer - dryRun: - description: DryRun specifies to perform a `kubectl apply --dry-run` - without actually performing the sync - type: boolean - manifests: - description: Manifests is an optional field that overrides sync - source with a local directory for development - items: - type: string - type: array - prune: - description: Prune specifies to delete resources from the cluster - that are no longer tracked in git - type: boolean - resources: - description: Resources describes which resources shall be part - of the sync - items: - description: SyncOperationResource contains resources to sync. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - required: - - kind - - name - type: object - type: array - revision: - description: |- - Revision is the revision (Git) or chart version (Helm) which to sync the application to - If omitted, will use the revision specified in app spec. - type: string - revisions: - description: |- - Revisions is the list of revision (Git) or chart version (Helm) which to sync each source in sources field for the application to - If omitted, will use the revision specified in app spec. - items: - type: string - type: array - source: - description: |- - Source overrides the source definition set in the application. - This is typically set in a Rollback operation and is nil during a Sync operation - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded from - being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included during - manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable to - be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level Arguments - items: - description: JsonnetVar represents a variable to - be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to the - helm template - items: - description: HelmFileParameter is a file parameter that's - passed to helm template during manifest generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm template - from failing when valueFiles do not exist locally by - not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to template - with. If left empty, defaults to the app's destination - namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters which - are passed to the helm template command upon manifest - generation - items: - description: HelmParameter is a parameter that's passed - to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether to tell - Helm to interpret booleans and numbers as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all domains - (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name to use. - If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema validation - (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. ValuesObject - takes precedence over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to be - passed to helm template, defined as a map. This takes - precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for templating - ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional labels - to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources for - Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to force - applying common labels to resources for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally by - not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize image - definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether to - apply common labels to resource templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether to - apply common labels to resource selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in the - kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in the - kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is displayed - in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable entries - items: - description: EnvEntry represents an entry in the application's - environment - properties: - name: - description: Name is the name of the variable, usually - expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array type - parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type parameter. - type: object - name: - description: Name is the name identifying a parameter. - type: string - string: - description: String_ is the value of a string type - parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within sources - field. This field will not be used if used with a `source` - tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git or - Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - sources: - description: |- - Sources overrides the source definition set in the application. - This is typically set in a Rollback operation and is nil during a Sync operation - items: - description: ApplicationSource contains all required information - about the source of an application - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded from - being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included during - manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to the - helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm template - from failing when valueFiles do not exist locally - by not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to template - with. If left empty, defaults to the app's destination - namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command upon - manifest generation - items: - description: HelmParameter is a parameter that's passed - to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether to - tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all - domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name to - use. If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. ValuesObject - takes precedence over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to be - passed to helm template, defined as a map. This takes - precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for - templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation - values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional labels - to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to - force applying common labels to resources for Kustomize - apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in - the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in - the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is displayed - in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable entries - items: - description: EnvEntry represents an entry in the application's - environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array type - parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type parameter. - type: object - name: - description: Name is the name identifying a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within sources - field. This field will not be used if used with a `source` - tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git or - Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - type: array - syncOptions: - description: SyncOptions provide per-sync sync-options, e.g. Validate=false - items: - type: string - type: array - syncStrategy: - description: SyncStrategy describes how to perform the sync - properties: - apply: - description: Apply will perform a `kubectl apply` to perform - the sync. - properties: - force: - description: |- - Force indicates whether or not to supply the --force flag to `kubectl apply`. - The --force flag deletes and re-create the resource, when PATCH encounters conflict and has - retried for 5 times. - type: boolean - type: object - hook: - description: Hook will submit any referenced resources to - perform the sync. This is the default strategy - properties: - force: - description: |- - Force indicates whether or not to supply the --force flag to `kubectl apply`. - The --force flag deletes and re-create the resource, when PATCH encounters conflict and has - retried for 5 times. - type: boolean - type: object - type: object - type: object - type: object - spec: - description: ApplicationSpec represents desired application state. Contains - link to repository with application definition and additional parameters - link definition revision. - properties: - destination: - description: Destination is a reference to the target Kubernetes server - and namespace - properties: - name: - description: Name is an alternate way of specifying the target - cluster by its symbolic name. This must be set if Server is - not set. - type: string - namespace: - description: |- - Namespace specifies the target namespace for the application's resources. - The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace - type: string - server: - description: Server specifies the URL of the target cluster's - Kubernetes control plane API. This must be set if Name is not - set. - type: string - type: object - ignoreDifferences: - description: IgnoreDifferences is a list of resources and their fields - which should be ignored during comparison - items: - description: ResourceIgnoreDifferences contains resource filter - and list of json paths which should be ignored during comparison - with live state. - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - description: |- - ManagedFieldsManagers is a list of trusted managers. Fields mutated by those managers will take precedence over the - desired state defined in the SCM and won't be displayed in diffs - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - description: Info contains a list of information (URLs, email addresses, - and plain text) that relates to the application - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - description: |- - Project is a reference to the project this application belongs to. - The empty string means that application belongs to the 'default' project. - type: string - revisionHistoryLimit: - description: |- - RevisionHistoryLimit limits the number of items kept in the application's revision history, which is used for informational purposes as well as for rollbacks to previous versions. - This should only be changed in exceptional circumstances. - Setting to zero will store no history. This will reduce storage used. - Increasing will increase the space used to store the history, so we do not recommend increasing it. - Default is 10. - format: int64 - type: integer - source: - description: Source is a reference to the location of the application's - manifests or chart - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match paths - against that should be explicitly excluded from being used - during manifest generation - type: string - include: - description: Include contains a glob pattern to match paths - against that should be explicitly included during manifest - generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External Variables - items: - description: JsonnetVar represents a variable to be - passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level Arguments - items: - description: JsonnetVar represents a variable to be - passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to the helm - template - items: - description: HelmFileParameter is a file parameter that's - passed to helm template during manifest generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm template - from failing when valueFiles do not exist locally by not - appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to template - with. If left empty, defaults to the app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters which - are passed to the helm template command upon manifest generation - items: - description: HelmParameter is a parameter that's passed - to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether to tell - Helm to interpret booleans and numbers as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all domains - (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name to use. - If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition installation - step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema validation - (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation step - (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files to - use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed to - helm template, typically defined as a block. ValuesObject - takes precedence over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to be passed - to helm template, defined as a map. This takes precedence - over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for templating - ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional annotations - to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether to - apply env variables substitution for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional labels to - add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize components - to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether to force - applying common annotations to resources for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to force - applying common labels to resources for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize from - failing when components do not exist locally by not appending - them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize image - definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether to apply - common labels to resource templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether to apply - common labels to resource selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in the kustomization.yaml - for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in the kustomization.yaml - for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize adds - to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas override - specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize to - use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is displayed - in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific options - properties: - env: - description: Env is a list of environment variable entries - items: - description: EnvEntry represents an entry in the application's - environment - properties: - name: - description: Name is the name of the variable, usually - expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type parameter. - type: object - name: - description: Name is the name identifying a parameter. - type: string - string: - description: String_ is the value of a string type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within sources - field. This field will not be used if used with a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git or Helm) - that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - sourceHydrator: - description: SourceHydrator provides a way to push hydrated manifests - back to git before syncing them to the cluster. - properties: - drySource: - description: DrySource specifies where the dry "don't repeat yourself" - manifest source lives. - properties: - directory: - description: Directory specifies path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded from - being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included during - manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable to - be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level Arguments - items: - description: JsonnetVar represents a variable to - be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm specifies helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to the - helm template - items: - description: HelmFileParameter is a file parameter that's - passed to helm template during manifest generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm template - from failing when valueFiles do not exist locally by - not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to template - with. If left empty, defaults to the app's destination - namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters which - are passed to the helm template command upon manifest - generation - items: - description: HelmParameter is a parameter that's passed - to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether to tell - Helm to interpret booleans and numbers as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all domains - (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name to use. - If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema validation - (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. ValuesObject - takes precedence over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to be - passed to helm template, defined as a map. This takes - precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for templating - ("3") - type: string - type: object - kustomize: - description: Kustomize specifies kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional labels - to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources for - Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to force - applying common labels to resources for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally by - not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize image - definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether to - apply common labels to resource templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether to - apply common labels to resource selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in the - kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in the - kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - path: - description: Path is a directory path within the Git repository - where the manifests are located - type: string - plugin: - description: Plugin specifies config management plugin specific - options - properties: - env: - description: Env is a list of environment variable entries - items: - description: EnvEntry represents an entry in the application's - environment - properties: - name: - description: Name is the name of the variable, usually - expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array type - parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type parameter. - type: object - name: - description: Name is the name identifying a parameter. - type: string - string: - description: String_ is the value of a string type - parameter. - type: string - type: object - type: array - type: object - repoURL: - description: RepoURL is the URL to the git repository that - contains the application manifests - type: string - targetRevision: - description: TargetRevision defines the revision of the source - to hydrate - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - description: |- - HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then - have to move manifests to the SyncSource, e.g. by pull request. - properties: - targetBranch: - description: TargetBranch is the branch to which hydrated - manifests should be committed - type: string - required: - - targetBranch - type: object - syncSource: - description: SyncSource specifies where to sync hydrated manifests - from. - properties: - path: - description: |- - Path is a directory path within the git repository where hydrated manifests should be committed to and synced - from. The Path should never point to the root of the repo. If hydrateTo is set, this is just the path from which - hydrated manifests will be synced. - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - description: |- - TargetBranch is the branch from which hydrated manifests will be synced. - If HydrateTo is not set, this is also the branch to which hydrated manifests are committed. - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - description: Sources is a reference to the location of the application's - manifests or chart - items: - description: ApplicationSource contains all required information - about the source of an application - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match paths - against that should be explicitly excluded from being - used during manifest generation - type: string - include: - description: Include contains a glob pattern to match paths - against that should be explicitly included during manifest - generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External Variables - items: - description: JsonnetVar represents a variable to be - passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level Arguments - items: - description: JsonnetVar represents a variable to be - passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to the helm - template - items: - description: HelmFileParameter is a file parameter that's - passed to helm template during manifest generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm template - from failing when valueFiles do not exist locally by not - appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to template - with. If left empty, defaults to the app's destination - namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters which - are passed to the helm template command upon manifest - generation - items: - description: HelmParameter is a parameter that's passed - to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether to tell - Helm to interpret booleans and numbers as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all domains - (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name to use. - If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition installation - step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema validation - (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files to - use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed to - helm template, typically defined as a block. ValuesObject - takes precedence over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to be passed - to helm template, defined as a map. This takes precedence - over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for templating - ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional annotations - to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional labels - to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize components - to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether to - force applying common annotations to resources for Kustomize - apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to force - applying common labels to resources for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally by not - appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize image - definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether to - apply common labels to resource templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether to apply - common labels to resource selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in the - kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in the - kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas override - specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is displayed - in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable entries - items: - description: EnvEntry represents an entry in the application's - environment - properties: - name: - description: Name is the name of the variable, usually - expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type parameter. - type: object - name: - description: Name is the name identifying a parameter. - type: string - string: - description: String_ is the value of a string type - parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within sources - field. This field will not be used if used with a `source` - tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git or Helm) - that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - description: SyncPolicy controls when and how a sync will be performed - properties: - automated: - description: Automated will keep an application synced to the - target revision - properties: - allowEmpty: - description: 'AllowEmpty allows apps have zero live resources - (default: false)' - type: boolean - enabled: - description: Enable allows apps to explicitly control automated - sync - type: boolean - prune: - description: 'Prune specifies whether to delete resources - from the cluster that are not found in the sources anymore - as part of automated sync (default: false)' - type: boolean - selfHeal: - description: 'SelfHeal specifies whether to revert resources - back to their desired state upon modification in the cluster - (default: false)' - type: boolean - type: object - managedNamespaceMetadata: - description: ManagedNamespaceMetadata controls metadata in the - given namespace (if CreateNamespace=true) - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - description: Retry controls failed sync retry behavior - properties: - backoff: - description: Backoff controls how to backoff on subsequent - retries of failed syncs - properties: - duration: - description: Duration is the amount to back off. Default - unit is seconds, but could also be a duration (e.g. - "2m", "1h") - type: string - factor: - description: Factor is a factor to multiply the base duration - after each failed retry - format: int64 - type: integer - maxDuration: - description: MaxDuration is the maximum amount of time - allowed for the backoff strategy - type: string - type: object - limit: - description: Limit is the maximum number of attempts for retrying - a failed sync. If set to 0, no retries will be performed. - format: int64 - type: integer - refresh: - description: 'Refresh indicates if the latest revision should - be used on retry instead of the initial one (default: false)' - type: boolean - type: object - syncOptions: - description: Options allow you to specify whole app sync-options - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - status: - description: ApplicationStatus contains status information for the application - properties: - conditions: - description: Conditions is a list of currently observed application - conditions - items: - description: ApplicationCondition contains details about an application - condition, which is usually an error or warning - properties: - lastTransitionTime: - description: LastTransitionTime is the time the condition was - last observed - format: date-time - type: string - message: - description: Message contains human-readable message indicating - details about condition - type: string - type: - description: Type is an application condition type - type: string - required: - - message - - type - type: object - type: array - controllerNamespace: - description: ControllerNamespace indicates the namespace in which - the application controller is located - type: string - health: - description: Health contains information about the application's current - health status - properties: - lastTransitionTime: - description: LastTransitionTime is the time the HealthStatus was - set or updated - format: date-time - type: string - message: - description: |- - Message is a human-readable informational message describing the health status - - Deprecated: this field is not used and will be removed in a future release. - type: string - status: - description: Status holds the status code of the application - type: string - type: object - history: - description: History contains information about the application's - sync history - items: - description: RevisionHistory contains history information about - a previous sync - properties: - deployStartedAt: - description: DeployStartedAt holds the time the sync operation - started - format: date-time - type: string - deployedAt: - description: DeployedAt holds the time the sync operation completed - format: date-time - type: string - id: - description: ID is an auto incrementing identifier of the RevisionHistory - format: int64 - type: integer - initiatedBy: - description: InitiatedBy contains information about who initiated - the operations - properties: - automated: - description: Automated is set to true if operation was initiated - automatically by the application controller. - type: boolean - username: - description: Username contains the name of a user who started - operation - type: string - type: object - revision: - description: Revision holds the revision the sync was performed - against - type: string - revisions: - description: Revisions holds the revision of each source in - sources field the sync was performed against - items: - type: string - type: array - source: - description: Source is a reference to the application source - used for the sync operation - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded from - being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included during - manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to the - helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm template - from failing when valueFiles do not exist locally - by not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to template - with. If left empty, defaults to the app's destination - namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command upon - manifest generation - items: - description: HelmParameter is a parameter that's passed - to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether to - tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all - domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name to - use. If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. ValuesObject - takes precedence over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to be - passed to helm template, defined as a map. This takes - precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for - templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation - values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional labels - to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to - force applying common labels to resources for Kustomize - apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in - the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in - the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is displayed - in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable entries - items: - description: EnvEntry represents an entry in the application's - environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array type - parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type parameter. - type: object - name: - description: Name is the name identifying a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within sources - field. This field will not be used if used with a `source` - tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git or - Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - sources: - description: Sources is a reference to the application sources - used for the sync operation - items: - description: ApplicationSource contains all required information - about the source of an application - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded - from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included - during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to - the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm - template from failing when valueFiles do not exist - locally by not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to - template with. If left empty, defaults to the app's - destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command upon - manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm - parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all - domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. - ValuesObject takes precedence over Values, so use - one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to - be passed to helm template, defined as a map. This - takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for - templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation - values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to - force applying common labels to resources for Kustomize - apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or - not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or - not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in - the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in - the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is - displayed in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in the - application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying a - parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within - sources field. This field will not be used if used with - a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git - or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - type: array - required: - - deployedAt - - id - type: object - type: array - observedAt: - description: |- - ObservedAt indicates when the application state was updated without querying latest git state - - Deprecated: controller no longer updates ObservedAt field - format: date-time - type: string - operationState: - description: OperationState contains information about any ongoing - operations, such as a sync - properties: - finishedAt: - description: FinishedAt contains time of operation completion - format: date-time - type: string - message: - description: Message holds any pertinent messages when attempting - to perform operation (typically errors). - type: string - operation: - description: Operation is the original requested operation - properties: - info: - description: Info is a list of informational items for this - operation - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - initiatedBy: - description: InitiatedBy contains information about who initiated - the operations - properties: - automated: - description: Automated is set to true if operation was - initiated automatically by the application controller. - type: boolean - username: - description: Username contains the name of a user who - started operation - type: string - type: object - retry: - description: Retry controls the strategy to apply if a sync - fails - properties: - backoff: - description: Backoff controls how to backoff on subsequent - retries of failed syncs - properties: - duration: - description: Duration is the amount to back off. Default - unit is seconds, but could also be a duration (e.g. - "2m", "1h") - type: string - factor: - description: Factor is a factor to multiply the base - duration after each failed retry - format: int64 - type: integer - maxDuration: - description: MaxDuration is the maximum amount of - time allowed for the backoff strategy - type: string - type: object - limit: - description: Limit is the maximum number of attempts for - retrying a failed sync. If set to 0, no retries will - be performed. - format: int64 - type: integer - refresh: - description: 'Refresh indicates if the latest revision - should be used on retry instead of the initial one (default: - false)' - type: boolean - type: object - sync: - description: Sync contains parameters for the operation - properties: - autoHealAttemptsCount: - description: SelfHealAttemptsCount contains the number - of auto-heal attempts - format: int64 - type: integer - dryRun: - description: DryRun specifies to perform a `kubectl apply - --dry-run` without actually performing the sync - type: boolean - manifests: - description: Manifests is an optional field that overrides - sync source with a local directory for development - items: - type: string - type: array - prune: - description: Prune specifies to delete resources from - the cluster that are no longer tracked in git - type: boolean - resources: - description: Resources describes which resources shall - be part of the sync - items: - description: SyncOperationResource contains resources - to sync. - properties: - group: - type: string - kind: - type: string - name: - type: string - namespace: - type: string - required: - - kind - - name - type: object - type: array - revision: - description: |- - Revision is the revision (Git) or chart version (Helm) which to sync the application to - If omitted, will use the revision specified in app spec. - type: string - revisions: - description: |- - Revisions is the list of revision (Git) or chart version (Helm) which to sync each source in sources field for the application to - If omitted, will use the revision specified in app spec. - items: - type: string - type: array - source: - description: |- - Source overrides the source definition set in the application. - This is typically set in a Rollback operation and is nil during a Sync operation - properties: - chart: - description: Chart is a Helm chart name, and must - be specified for applications sourced from a Helm - repo. - type: string - directory: - description: Directory holds path/directory specific - options - properties: - exclude: - description: Exclude contains a glob pattern to - match paths against that should be explicitly - excluded from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to - match paths against that should be explicitly - included during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to - Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet - External Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan - a directory recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters - to the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm - parameter - type: string - path: - description: Path is the path to the file - containing the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents - helm template from failing when valueFiles do - not exist locally by not appending them to helm - template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace - to template with. If left empty, defaults to - the app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command - upon manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and - numbers as strings - type: boolean - name: - description: Name is the name of the Helm - parameter - type: string - value: - description: Value is the value for the - Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials - to all domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application - name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value - files to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be - passed to helm template, typically defined as - a block. ValuesObject takes precedence over - Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values - to be passed to helm template, defined as a - map. This takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use - for templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies - whether to apply env variables substitution - for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before - building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies - whether to force applying common annotations - to resources for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether - to force applying common labels to resources - for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents - kustomize from failing when components do not - exist locally by not appending them to kustomization - file - type: boolean - images: - description: Images is a list of Kustomize image - override specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates - or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors - or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix - in the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix - in the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that - Kustomize adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of - Kustomize to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and - is displayed in the UI. It is used in multi-source - Applications. - type: string - path: - description: Path is a directory path within the Git - repository, and is only valid for applications sourced - from Git. - type: string - plugin: - description: Plugin holds config management plugin - specific options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in - the application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying - a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within - sources field. This field will not be used if used - with a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository - (Git or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - sources: - description: |- - Sources overrides the source definition set in the application. - This is typically set in a Rollback operation and is nil during a Sync operation - items: - description: ApplicationSource contains all required - information about the source of an application - properties: - chart: - description: Chart is a Helm chart name, and must - be specified for applications sourced from a Helm - repo. - type: string - directory: - description: Directory holds path/directory specific - options - properties: - exclude: - description: Exclude contains a glob pattern - to match paths against that should be explicitly - excluded from being used during manifest generation - type: string - include: - description: Include contains a glob pattern - to match paths against that should be explicitly - included during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific - to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet - External Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan - a directory recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters - to the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm - parameter - type: string - path: - description: Path is the path to the file - containing the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents - helm template from failing when valueFiles - do not exist locally by not appending them - to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace - to template with. If left empty, defaults - to the app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command - upon manifest generation - items: - description: HelmParameter is a parameter - that's passed to helm template during manifest - generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and - numbers as strings - type: boolean - name: - description: Name is the name of the Helm - parameter - type: string - value: - description: Value is the value for the - Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials - to all domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release - name to use. If omitted it will use the application - name - type: string - skipCrds: - description: SkipCrds skips custom resource - definition installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON - schema validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value - files to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to - be passed to helm template, typically defined - as a block. ValuesObject takes precedence - over Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values - to be passed to helm template, defined as - a map. This takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to - use for templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific - options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of - additional annotations to add to rendered - manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies - whether to apply env variables substitution - for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of - kustomize components to add to the kustomization - before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies - whether to force applying common annotations - to resources for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether - to force applying common labels to resources - for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents - kustomize from failing when components do - not exist locally by not appending them to - kustomization file - type: boolean - images: - description: Images is a list of Kustomize image - override specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies - whether to apply common labels to resource - templates or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies - whether to apply common labels to resource - selectors or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix - in the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix - in the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that - Kustomize adds to all resources - type: string - patches: - description: Patches is a list of Kustomize - patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize - Replicas override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version - of Kustomize to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and - is displayed in the UI. It is used in multi-source - Applications. - type: string - path: - description: Path is a directory path within the - Git repository, and is only valid for applications - sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin - specific options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry - in the application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the - variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an - array type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map - type parameter. - type: object - name: - description: Name is the name identifying - a parameter. - type: string - string: - description: String_ is the value of a - string type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source - within sources field. This field will not be used - if used with a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository - (Git or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - type: array - syncOptions: - description: SyncOptions provide per-sync sync-options, - e.g. Validate=false - items: - type: string - type: array - syncStrategy: - description: SyncStrategy describes how to perform the - sync - properties: - apply: - description: Apply will perform a `kubectl apply` - to perform the sync. - properties: - force: - description: |- - Force indicates whether or not to supply the --force flag to `kubectl apply`. - The --force flag deletes and re-create the resource, when PATCH encounters conflict and has - retried for 5 times. - type: boolean - type: object - hook: - description: Hook will submit any referenced resources - to perform the sync. This is the default strategy - properties: - force: - description: |- - Force indicates whether or not to supply the --force flag to `kubectl apply`. - The --force flag deletes and re-create the resource, when PATCH encounters conflict and has - retried for 5 times. - type: boolean - type: object - type: object - type: object - type: object - phase: - description: Phase is the current phase of the operation - type: string - retryCount: - description: RetryCount contains time of operation retries - format: int64 - type: integer - startedAt: - description: StartedAt contains time of operation start - format: date-time - type: string - syncResult: - description: SyncResult is the result of a Sync operation - properties: - managedNamespaceMetadata: - description: ManagedNamespaceMetadata contains the current - sync state of managed namespace metadata - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - resources: - description: Resources contains a list of sync result items - for each individual resource in a sync operation - items: - description: ResourceResult holds the operation result details - of a specific resource - properties: - group: - description: Group specifies the API group of the resource - type: string - hookPhase: - description: |- - HookPhase contains the state of any operation associated with this resource OR hook - This can also contain values for non-hook resources. - type: string - hookType: - description: HookType specifies the type of the hook. - Empty for non-hook resources - type: string - images: - description: Images contains the images related to the - ResourceResult - items: - type: string - type: array - kind: - description: Kind specifies the API kind of the resource - type: string - message: - description: Message contains an informational or error - message for the last sync OR operation - type: string - name: - description: Name specifies the name of the resource - type: string - namespace: - description: Namespace specifies the target namespace - of the resource - type: string - status: - description: Status holds the final result of the sync. - Will be empty if the resources is yet to be applied/pruned - and is always zero-value for hooks - type: string - syncPhase: - description: SyncPhase indicates the particular phase - of the sync that this result was acquired in - type: string - version: - description: Version specifies the API version of the - resource - type: string - required: - - group - - kind - - name - - namespace - - version - type: object - type: array - revision: - description: Revision holds the revision this sync operation - was performed to - type: string - revisions: - description: Revisions holds the revision this sync operation - was performed for respective indexed source in sources field - items: - type: string - type: array - source: - description: Source records the application source information - of the sync, used for comparing auto-sync - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded - from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included - during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to - the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm - template from failing when valueFiles do not exist - locally by not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to - template with. If left empty, defaults to the app's - destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command upon - manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm - parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all - domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. - ValuesObject takes precedence over Values, so use - one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to - be passed to helm template, defined as a map. This - takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for - templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation - values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to - force applying common labels to resources for Kustomize - apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or - not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or - not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in - the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in - the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is - displayed in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in the - application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying a - parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within - sources field. This field will not be used if used with - a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git - or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - sources: - description: Source records the application source information - of the sync, used for comparing auto-sync - items: - description: ApplicationSource contains all required information - about the source of an application - properties: - chart: - description: Chart is a Helm chart name, and must be - specified for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific - options - properties: - exclude: - description: Exclude contains a glob pattern to - match paths against that should be explicitly - excluded from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to - match paths against that should be explicitly - included during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a - directory recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters - to the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm - parameter - type: string - path: - description: Path is the path to the file - containing the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm - template from failing when valueFiles do not exist - locally by not appending them to helm template - --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace - to template with. If left empty, defaults to the - app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command - upon manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm - parameter - type: string - value: - description: Value is the value for the Helm - parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to - all domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application - name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value - files to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be - passed to helm template, typically defined as - a block. ValuesObject takes precedence over Values, - so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values - to be passed to helm template, defined as a map. - This takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use - for templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies - whether to apply env variables substitution for - annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before - building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether - to force applying common labels to resources for - Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image - override specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or - not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or - not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix - in the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix - in the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is - displayed in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git - repository, and is only valid for applications sourced - from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in the - application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying - a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within - sources field. This field will not be used if used - with a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git - or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - type: array - required: - - revision - type: object - required: - - operation - - phase - - startedAt - type: object - reconciledAt: - description: ReconciledAt indicates when the application state was - reconciled using the latest git version - format: date-time - type: string - resourceHealthSource: - description: 'ResourceHealthSource indicates where the resource health - status is stored: inline if not set or appTree' - type: string - resources: - description: Resources is a list of Kubernetes resources managed by - this application - items: - description: ResourceStatus holds the current synchronization and - health status of a Kubernetes resource. - properties: - group: - description: Group represents the API group of the resource - (e.g., "apps" for Deployments). - type: string - health: - description: Health indicates the health status of the resource - (e.g., Healthy, Degraded, Progressing). - properties: - lastTransitionTime: - description: |- - LastTransitionTime is the time the HealthStatus was set or updated - - Deprecated: this field is not used and will be removed in a future release. - format: date-time - type: string - message: - description: Message is a human-readable informational message - describing the health status - type: string - status: - description: Status holds the status code of the resource - type: string - type: object - hook: - description: Hook is true if the resource is used as a lifecycle - hook in an Argo CD application. - type: boolean - kind: - description: Kind specifies the type of the resource (e.g., - "Deployment", "Service"). - type: string - name: - description: Name is the unique name of the resource within - the namespace. - type: string - namespace: - description: Namespace defines the Kubernetes namespace where - the resource is located. - type: string - requiresDeletionConfirmation: - description: RequiresDeletionConfirmation is true if the resource - requires explicit user confirmation before deletion. - type: boolean - requiresPruning: - description: RequiresPruning is true if the resource needs to - be pruned (deleted) as part of synchronization. - type: boolean - status: - description: Status represents the synchronization state of - the resource (e.g., Synced, OutOfSync). - type: string - syncWave: - description: |- - SyncWave determines the order in which resources are applied during a sync operation. - Lower values are applied first. - format: int64 - type: integer - version: - description: Version indicates the API version of the resource - (e.g., "v1", "v1beta1"). - type: string - type: object - type: array - sourceHydrator: - description: SourceHydrator stores information about the current state - of source hydration - properties: - currentOperation: - description: CurrentOperation holds the status of the hydrate - operation - properties: - drySHA: - description: DrySHA holds the resolved revision (sha) of the - dry source as of the most recent reconciliation - type: string - finishedAt: - description: FinishedAt indicates when the hydrate operation - finished - format: date-time - type: string - hydratedSHA: - description: HydratedSHA holds the resolved revision (sha) - of the hydrated source as of the most recent reconciliation - type: string - message: - description: Message contains a message describing the current - status of the hydrate operation - type: string - phase: - description: Phase indicates the status of the hydrate operation - enum: - - Hydrating - - Failed - - Hydrated - type: string - sourceHydrator: - description: SourceHydrator holds the hydrator config used - for the hydrate operation - properties: - drySource: - description: DrySource specifies where the dry "don't - repeat yourself" manifest source lives. - properties: - directory: - description: Directory specifies path/directory specific - options - properties: - exclude: - description: Exclude contains a glob pattern to - match paths against that should be explicitly - excluded from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to - match paths against that should be explicitly - included during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to - Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet - External Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan - a directory recursively for manifests - type: boolean - type: object - helm: - description: Helm specifies helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters - to the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm - parameter - type: string - path: - description: Path is the path to the file - containing the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents - helm template from failing when valueFiles do - not exist locally by not appending them to helm - template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace - to template with. If left empty, defaults to - the app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command - upon manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and - numbers as strings - type: boolean - name: - description: Name is the name of the Helm - parameter - type: string - value: - description: Value is the value for the - Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials - to all domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application - name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value - files to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be - passed to helm template, typically defined as - a block. ValuesObject takes precedence over - Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values - to be passed to helm template, defined as a - map. This takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use - for templating ("3") - type: string - type: object - kustomize: - description: Kustomize specifies kustomize specific - options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies - whether to apply env variables substitution - for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before - building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies - whether to force applying common annotations - to resources for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether - to force applying common labels to resources - for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents - kustomize from failing when components do not - exist locally by not appending them to kustomization - file - type: boolean - images: - description: Images is a list of Kustomize image - override specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates - or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors - or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix - in the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix - in the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that - Kustomize adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of - Kustomize to use for rendering manifests - type: string - type: object - path: - description: Path is a directory path within the Git - repository where the manifests are located - type: string - plugin: - description: Plugin specifies config management plugin - specific options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in - the application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying - a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - repoURL: - description: RepoURL is the URL to the git repository - that contains the application manifests - type: string - targetRevision: - description: TargetRevision defines the revision of - the source to hydrate - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - description: |- - HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then - have to move manifests to the SyncSource, e.g. by pull request. - properties: - targetBranch: - description: TargetBranch is the branch to which hydrated - manifests should be committed - type: string - required: - - targetBranch - type: object - syncSource: - description: SyncSource specifies where to sync hydrated - manifests from. - properties: - path: - description: |- - Path is a directory path within the git repository where hydrated manifests should be committed to and synced - from. The Path should never point to the root of the repo. If hydrateTo is set, this is just the path from which - hydrated manifests will be synced. - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - description: |- - TargetBranch is the branch from which hydrated manifests will be synced. - If HydrateTo is not set, this is also the branch to which hydrated manifests are committed. - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - startedAt: - description: StartedAt indicates when the hydrate operation - started - format: date-time - type: string - required: - - message - - phase - type: object - lastSuccessfulOperation: - description: LastSuccessfulOperation holds info about the most - recent successful hydration - properties: - drySHA: - description: DrySHA holds the resolved revision (sha) of the - dry source as of the most recent reconciliation - type: string - hydratedSHA: - description: HydratedSHA holds the resolved revision (sha) - of the hydrated source as of the most recent reconciliation - type: string - sourceHydrator: - description: SourceHydrator holds the hydrator config used - for the hydrate operation - properties: - drySource: - description: DrySource specifies where the dry "don't - repeat yourself" manifest source lives. - properties: - directory: - description: Directory specifies path/directory specific - options - properties: - exclude: - description: Exclude contains a glob pattern to - match paths against that should be explicitly - excluded from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to - match paths against that should be explicitly - included during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to - Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet - External Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan - a directory recursively for manifests - type: boolean - type: object - helm: - description: Helm specifies helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters - to the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm - parameter - type: string - path: - description: Path is the path to the file - containing the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents - helm template from failing when valueFiles do - not exist locally by not appending them to helm - template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace - to template with. If left empty, defaults to - the app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command - upon manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and - numbers as strings - type: boolean - name: - description: Name is the name of the Helm - parameter - type: string - value: - description: Value is the value for the - Helm parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials - to all domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application - name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value - files to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be - passed to helm template, typically defined as - a block. ValuesObject takes precedence over - Values, so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values - to be passed to helm template, defined as a - map. This takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use - for templating ("3") - type: string - type: object - kustomize: - description: Kustomize specifies kustomize specific - options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies - whether to apply env variables substitution - for annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before - building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies - whether to force applying common annotations - to resources for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether - to force applying common labels to resources - for Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents - kustomize from failing when components do not - exist locally by not appending them to kustomization - file - type: boolean - images: - description: Images is a list of Kustomize image - override specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates - or not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors - or not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix - in the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix - in the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that - Kustomize adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of - Kustomize to use for rendering manifests - type: string - type: object - path: - description: Path is a directory path within the Git - repository where the manifests are located - type: string - plugin: - description: Plugin specifies config management plugin - specific options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in - the application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying - a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - repoURL: - description: RepoURL is the URL to the git repository - that contains the application manifests - type: string - targetRevision: - description: TargetRevision defines the revision of - the source to hydrate - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - description: |- - HydrateTo specifies an optional "staging" location to push hydrated manifests to. An external system would then - have to move manifests to the SyncSource, e.g. by pull request. - properties: - targetBranch: - description: TargetBranch is the branch to which hydrated - manifests should be committed - type: string - required: - - targetBranch - type: object - syncSource: - description: SyncSource specifies where to sync hydrated - manifests from. - properties: - path: - description: |- - Path is a directory path within the git repository where hydrated manifests should be committed to and synced - from. The Path should never point to the root of the repo. If hydrateTo is set, this is just the path from which - hydrated manifests will be synced. - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - description: |- - TargetBranch is the branch from which hydrated manifests will be synced. - If HydrateTo is not set, this is also the branch to which hydrated manifests are committed. - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - type: object - type: object - sourceType: - description: SourceType specifies the type of this application - type: string - sourceTypes: - description: SourceTypes specifies the type of the sources included - in the application - items: - description: ApplicationSourceType specifies the type of the application's - source - type: string - type: array - summary: - description: Summary contains a list of URLs and container images - used by this application - properties: - externalURLs: - description: ExternalURLs holds all external URLs of application - child resources. - items: - type: string - type: array - images: - description: Images holds all images of application child resources. - items: - type: string - type: array - type: object - sync: - description: Sync contains information about the application's current - sync status - properties: - comparedTo: - description: ComparedTo contains information about what has been - compared - properties: - destination: - description: Destination is a reference to the application's - destination used for comparison - properties: - name: - description: Name is an alternate way of specifying the - target cluster by its symbolic name. This must be set - if Server is not set. - type: string - namespace: - description: |- - Namespace specifies the target namespace for the application's resources. - The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace - type: string - server: - description: Server specifies the URL of the target cluster's - Kubernetes control plane API. This must be set if Name - is not set. - type: string - type: object - ignoreDifferences: - description: IgnoreDifferences is a reference to the application's - ignored differences used for comparison - items: - description: ResourceIgnoreDifferences contains resource - filter and list of json paths which should be ignored - during comparison with live state. - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - description: |- - ManagedFieldsManagers is a list of trusted managers. Fields mutated by those managers will take precedence over the - desired state defined in the SCM and won't be displayed in diffs - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - source: - description: Source is a reference to the application's source - used for comparison - properties: - chart: - description: Chart is a Helm chart name, and must be specified - for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific options - properties: - exclude: - description: Exclude contains a glob pattern to match - paths against that should be explicitly excluded - from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to match - paths against that should be explicitly included - during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a directory - recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters to - the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm parameter - type: string - path: - description: Path is the path to the file containing - the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm - template from failing when valueFiles do not exist - locally by not appending them to helm template --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace to - template with. If left empty, defaults to the app's - destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command upon - manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm parameter - type: string - value: - description: Value is the value for the Helm - parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to all - domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value files - to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be passed - to helm template, typically defined as a block. - ValuesObject takes precedence over Values, so use - one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values to - be passed to helm template, defined as a map. This - takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use for - templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies whether - to apply env variables substitution for annotation - values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether to - force applying common labels to resources for Kustomize - apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image override - specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or - not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or - not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix in - the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix in - the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is - displayed in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in the - application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying a - parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within - sources field. This field will not be used if used with - a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git - or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - sources: - description: Sources is a reference to the application's multiple - sources used for comparison - items: - description: ApplicationSource contains all required information - about the source of an application - properties: - chart: - description: Chart is a Helm chart name, and must be - specified for applications sourced from a Helm repo. - type: string - directory: - description: Directory holds path/directory specific - options - properties: - exclude: - description: Exclude contains a glob pattern to - match paths against that should be explicitly - excluded from being used during manifest generation - type: string - include: - description: Include contains a glob pattern to - match paths against that should be explicitly - included during manifest generation - type: string - jsonnet: - description: Jsonnet holds options specific to Jsonnet - properties: - extVars: - description: ExtVars is a list of Jsonnet External - Variables - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - description: Additional library search dirs - items: - type: string - type: array - tlas: - description: TLAS is a list of Jsonnet Top-level - Arguments - items: - description: JsonnetVar represents a variable - to be passed to jsonnet during manifest - generation - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - description: Recurse specifies whether to scan a - directory recursively for manifests - type: boolean - type: object - helm: - description: Helm holds helm specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - fileParameters: - description: FileParameters are file parameters - to the helm template - items: - description: HelmFileParameter is a file parameter - that's passed to helm template during manifest - generation - properties: - name: - description: Name is the name of the Helm - parameter - type: string - path: - description: Path is the path to the file - containing the values for the Helm parameter - type: string - type: object - type: array - ignoreMissingValueFiles: - description: IgnoreMissingValueFiles prevents helm - template from failing when valueFiles do not exist - locally by not appending them to helm template - --values - type: boolean - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - namespace: - description: Namespace is an optional namespace - to template with. If left empty, defaults to the - app's destination namespace. - type: string - parameters: - description: Parameters is a list of Helm parameters - which are passed to the helm template command - upon manifest generation - items: - description: HelmParameter is a parameter that's - passed to helm template during manifest generation - properties: - forceString: - description: ForceString determines whether - to tell Helm to interpret booleans and numbers - as strings - type: boolean - name: - description: Name is the name of the Helm - parameter - type: string - value: - description: Value is the value for the Helm - parameter - type: string - type: object - type: array - passCredentials: - description: PassCredentials pass credentials to - all domains (Helm's --pass-credentials) - type: boolean - releaseName: - description: ReleaseName is the Helm release name - to use. If omitted it will use the application - name - type: string - skipCrds: - description: SkipCrds skips custom resource definition - installation step (Helm's --skip-crds) - type: boolean - skipSchemaValidation: - description: SkipSchemaValidation skips JSON schema - validation (Helm's --skip-schema-validation) - type: boolean - skipTests: - description: SkipTests skips test manifest installation - step (Helm's --skip-tests). - type: boolean - valueFiles: - description: ValuesFiles is a list of Helm value - files to use when generating a template - items: - type: string - type: array - values: - description: Values specifies Helm values to be - passed to helm template, typically defined as - a block. ValuesObject takes precedence over Values, - so use one or the other. - type: string - valuesObject: - description: ValuesObject specifies Helm values - to be passed to helm template, defined as a map. - This takes precedence over Values. - type: object - x-kubernetes-preserve-unknown-fields: true - version: - description: Version is the Helm version to use - for templating ("3") - type: string - type: object - kustomize: - description: Kustomize holds kustomize specific options - properties: - apiVersions: - description: |- - APIVersions specifies the Kubernetes resource API versions to pass to Helm when templating manifests. By default, - Argo CD uses the API versions of the target cluster. The format is [group/]version/kind. - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - description: CommonAnnotations is a list of additional - annotations to add to rendered manifests - type: object - commonAnnotationsEnvsubst: - description: CommonAnnotationsEnvsubst specifies - whether to apply env variables substitution for - annotation values - type: boolean - commonLabels: - additionalProperties: - type: string - description: CommonLabels is a list of additional - labels to add to rendered manifests - type: object - components: - description: Components specifies a list of kustomize - components to add to the kustomization before - building - items: - type: string - type: array - forceCommonAnnotations: - description: ForceCommonAnnotations specifies whether - to force applying common annotations to resources - for Kustomize apps - type: boolean - forceCommonLabels: - description: ForceCommonLabels specifies whether - to force applying common labels to resources for - Kustomize apps - type: boolean - ignoreMissingComponents: - description: IgnoreMissingComponents prevents kustomize - from failing when components do not exist locally - by not appending them to kustomization file - type: boolean - images: - description: Images is a list of Kustomize image - override specifications - items: - description: KustomizeImage represents a Kustomize - image definition in the format [old_image_name=]: - type: string - type: array - kubeVersion: - description: |- - KubeVersion specifies the Kubernetes API version to pass to Helm when templating manifests. By default, Argo CD - uses the Kubernetes version of the target cluster. - type: string - labelIncludeTemplates: - description: LabelIncludeTemplates specifies whether - to apply common labels to resource templates or - not - type: boolean - labelWithoutSelector: - description: LabelWithoutSelector specifies whether - to apply common labels to resource selectors or - not - type: boolean - namePrefix: - description: NamePrefix overrides the namePrefix - in the kustomization.yaml for Kustomize apps - type: string - nameSuffix: - description: NameSuffix overrides the nameSuffix - in the kustomization.yaml for Kustomize apps - type: string - namespace: - description: Namespace sets the namespace that Kustomize - adds to all resources - type: string - patches: - description: Patches is a list of Kustomize patches - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - description: Replicas is a list of Kustomize Replicas - override specifications - items: - properties: - count: - anyOf: - - type: integer - - type: string - description: Number of replicas - x-kubernetes-int-or-string: true - name: - description: Name of Deployment or StatefulSet - type: string - required: - - count - - name - type: object - type: array - version: - description: Version controls which version of Kustomize - to use for rendering manifests - type: string - type: object - name: - description: Name is used to refer to a source and is - displayed in the UI. It is used in multi-source Applications. - type: string - path: - description: Path is a directory path within the Git - repository, and is only valid for applications sourced - from Git. - type: string - plugin: - description: Plugin holds config management plugin specific - options - properties: - env: - description: Env is a list of environment variable - entries - items: - description: EnvEntry represents an entry in the - application's environment - properties: - name: - description: Name is the name of the variable, - usually expressed in uppercase - type: string - value: - description: Value is the value of the variable - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - description: Array is the value of an array - type parameter. - items: - type: string - type: array - map: - additionalProperties: - type: string - description: Map is the value of a map type - parameter. - type: object - name: - description: Name is the name identifying - a parameter. - type: string - string: - description: String_ is the value of a string - type parameter. - type: string - type: object - type: array - type: object - ref: - description: Ref is reference to another source within - sources field. This field will not be used if used - with a `source` tag. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git - or Helm) that contains the application manifests - type: string - targetRevision: - description: |- - TargetRevision defines the revision of the source to sync the application to. - In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - In case of Helm, this is a semver tag for the Chart's version. - type: string - required: - - repoURL - type: object - type: array - required: - - destination - type: object - revision: - description: Revision contains information about the revision - the comparison has been performed to - type: string - revisions: - description: Revisions contains information about the revisions - of multiple sources the comparison has been performed to - items: - type: string - type: array - status: - description: Status is the sync state of the comparison - type: string - required: - - status - type: object - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/name: applicationsets.argoproj.io - app.kubernetes.io/part-of: argocd - name: applicationsets.argoproj.io -spec: - group: argoproj.io - names: - kind: ApplicationSet - listKind: ApplicationSetList - plural: applicationsets - shortNames: - - appset - - appsets - singular: applicationset - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - properties: - apiVersion: - type: string - kind: - type: string - metadata: - type: object - spec: - properties: - applyNestedSelectors: - type: boolean - generators: - items: - properties: - clusterDecisionResource: - properties: - configMapRef: - type: string - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - name: - type: string - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - configMapRef - type: object - clusters: - properties: - flatList: - type: boolean - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - git: - properties: - directories: - items: - properties: - exclude: - type: boolean - path: - type: string - required: - - path - type: object - type: array - files: - items: - properties: - exclude: - type: boolean - path: - type: string - required: - - path - type: object - type: array - pathParamPrefix: - type: string - repoURL: - type: string - requeueAfterSeconds: - format: int64 - type: integer - revision: - type: string - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - repoURL - - revision - type: object - list: - properties: - elements: - items: - x-kubernetes-preserve-unknown-fields: true - type: array - elementsYaml: - type: string - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - type: object - matrix: - properties: - generators: - items: - properties: - clusterDecisionResource: - properties: - configMapRef: - type: string - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - name: - type: string - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - configMapRef - type: object - clusters: - properties: - flatList: - type: boolean - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - git: - properties: - directories: - items: - properties: - exclude: - type: boolean - path: - type: string - required: - - path - type: object - type: array - files: - items: - properties: - exclude: - type: boolean - path: - type: string - required: - - path - type: object - type: array - pathParamPrefix: - type: string - repoURL: - type: string - requeueAfterSeconds: - format: int64 - type: integer - revision: - type: string - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - repoURL - - revision - type: object - list: - properties: - elements: - items: - x-kubernetes-preserve-unknown-fields: true - type: array - elementsYaml: - type: string - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - type: object - matrix: - x-kubernetes-preserve-unknown-fields: true - merge: - x-kubernetes-preserve-unknown-fields: true - plugin: - properties: - configMapRef: - properties: - name: - type: string - required: - - name - type: object - input: - properties: - parameters: - additionalProperties: - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - configMapRef - type: object - pullRequest: - properties: - azuredevops: - properties: - api: - type: string - labels: - items: - type: string - type: array - organization: - type: string - project: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - organization - - project - - repo - type: object - bitbucket: - properties: - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - owner: - type: string - repo: - type: string - required: - - owner - - repo - type: object - bitbucketServer: - properties: - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - project: - type: string - repo: - type: string - required: - - api - - project - - repo - type: object - continueOnRepoNotFoundError: - type: boolean - filters: - items: - properties: - branchMatch: - type: string - targetBranchMatch: - type: string - titleMatch: - type: string - type: object - type: array - gitea: - properties: - api: - type: string - insecure: - type: boolean - labels: - items: - type: string - type: array - owner: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - api - - owner - - repo - type: object - github: - properties: - api: - type: string - appSecretName: - type: string - labels: - items: - type: string - type: array - owner: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - owner - - repo - type: object - gitlab: - properties: - api: - type: string - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - labels: - items: - type: string - type: array - project: - type: string - pullRequestState: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - project - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - scmProvider: - properties: - awsCodeCommit: - properties: - allBranches: - type: boolean - region: - type: string - role: - type: string - tagFilters: - items: - properties: - key: - type: string - value: - type: string - required: - - key - type: object - type: array - type: object - azureDevOps: - properties: - accessTokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - allBranches: - type: boolean - api: - type: string - organization: - type: string - teamProject: - type: string - required: - - accessTokenRef - - organization - - teamProject - type: object - bitbucket: - properties: - allBranches: - type: boolean - appPasswordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - owner: - type: string - user: - type: string - required: - - appPasswordRef - - owner - - user - type: object - bitbucketServer: - properties: - allBranches: - type: boolean - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - project: - type: string - required: - - api - - project - type: object - cloneProtocol: - type: string - filters: - items: - properties: - branchMatch: - type: string - labelMatch: - type: string - pathsDoNotExist: - items: - type: string - type: array - pathsExist: - items: - type: string - type: array - repositoryMatch: - type: string - type: object - type: array - gitea: - properties: - allBranches: - type: boolean - api: - type: string - insecure: - type: boolean - owner: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - api - - owner - type: object - github: - properties: - allBranches: - type: boolean - api: - type: string - appSecretName: - type: string - organization: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - organization - type: object - gitlab: - properties: - allBranches: - type: boolean - api: - type: string - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - group: - type: string - includeSharedProjects: - type: boolean - includeSubgroups: - type: boolean - insecure: - type: boolean - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - topic: - type: string - required: - - group - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - required: - - generators - type: object - merge: - properties: - generators: - items: - properties: - clusterDecisionResource: - properties: - configMapRef: - type: string - labelSelector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - name: - type: string - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - configMapRef - type: object - clusters: - properties: - flatList: - type: boolean - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - git: - properties: - directories: - items: - properties: - exclude: - type: boolean - path: - type: string - required: - - path - type: object - type: array - files: - items: - properties: - exclude: - type: boolean - path: - type: string - required: - - path - type: object - type: array - pathParamPrefix: - type: string - repoURL: - type: string - requeueAfterSeconds: - format: int64 - type: integer - revision: - type: string - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - repoURL - - revision - type: object - list: - properties: - elements: - items: - x-kubernetes-preserve-unknown-fields: true - type: array - elementsYaml: - type: string - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - type: object - matrix: - x-kubernetes-preserve-unknown-fields: true - merge: - x-kubernetes-preserve-unknown-fields: true - plugin: - properties: - configMapRef: - properties: - name: - type: string - required: - - name - type: object - input: - properties: - parameters: - additionalProperties: - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - configMapRef - type: object - pullRequest: - properties: - azuredevops: - properties: - api: - type: string - labels: - items: - type: string - type: array - organization: - type: string - project: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - organization - - project - - repo - type: object - bitbucket: - properties: - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - owner: - type: string - repo: - type: string - required: - - owner - - repo - type: object - bitbucketServer: - properties: - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - project: - type: string - repo: - type: string - required: - - api - - project - - repo - type: object - continueOnRepoNotFoundError: - type: boolean - filters: - items: - properties: - branchMatch: - type: string - targetBranchMatch: - type: string - titleMatch: - type: string - type: object - type: array - gitea: - properties: - api: - type: string - insecure: - type: boolean - labels: - items: - type: string - type: array - owner: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - api - - owner - - repo - type: object - github: - properties: - api: - type: string - appSecretName: - type: string - labels: - items: - type: string - type: array - owner: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - owner - - repo - type: object - gitlab: - properties: - api: - type: string - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - labels: - items: - type: string - type: array - project: - type: string - pullRequestState: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - project - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - scmProvider: - properties: - awsCodeCommit: - properties: - allBranches: - type: boolean - region: - type: string - role: - type: string - tagFilters: - items: - properties: - key: - type: string - value: - type: string - required: - - key - type: object - type: array - type: object - azureDevOps: - properties: - accessTokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - allBranches: - type: boolean - api: - type: string - organization: - type: string - teamProject: - type: string - required: - - accessTokenRef - - organization - - teamProject - type: object - bitbucket: - properties: - allBranches: - type: boolean - appPasswordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - owner: - type: string - user: - type: string - required: - - appPasswordRef - - owner - - user - type: object - bitbucketServer: - properties: - allBranches: - type: boolean - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - project: - type: string - required: - - api - - project - type: object - cloneProtocol: - type: string - filters: - items: - properties: - branchMatch: - type: string - labelMatch: - type: string - pathsDoNotExist: - items: - type: string - type: array - pathsExist: - items: - type: string - type: array - repositoryMatch: - type: string - type: object - type: array - gitea: - properties: - allBranches: - type: boolean - api: - type: string - insecure: - type: boolean - owner: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - api - - owner - type: object - github: - properties: - allBranches: - type: boolean - api: - type: string - appSecretName: - type: string - organization: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - organization - type: object - gitlab: - properties: - allBranches: - type: boolean - api: - type: string - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - group: - type: string - includeSharedProjects: - type: boolean - includeSubgroups: - type: boolean - insecure: - type: boolean - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - topic: - type: string - required: - - group - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - mergeKeys: - items: - type: string - type: array - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - required: - - generators - - mergeKeys - type: object - plugin: - properties: - configMapRef: - properties: - name: - type: string - required: - - name - type: object - input: - properties: - parameters: - additionalProperties: - x-kubernetes-preserve-unknown-fields: true - type: object - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - required: - - configMapRef - type: object - pullRequest: - properties: - azuredevops: - properties: - api: - type: string - labels: - items: - type: string - type: array - organization: - type: string - project: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - organization - - project - - repo - type: object - bitbucket: - properties: - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - owner: - type: string - repo: - type: string - required: - - owner - - repo - type: object - bitbucketServer: - properties: - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - project: - type: string - repo: - type: string - required: - - api - - project - - repo - type: object - continueOnRepoNotFoundError: - type: boolean - filters: - items: - properties: - branchMatch: - type: string - targetBranchMatch: - type: string - titleMatch: - type: string - type: object - type: array - gitea: - properties: - api: - type: string - insecure: - type: boolean - labels: - items: - type: string - type: array - owner: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - api - - owner - - repo - type: object - github: - properties: - api: - type: string - appSecretName: - type: string - labels: - items: - type: string - type: array - owner: - type: string - repo: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - owner - - repo - type: object - gitlab: - properties: - api: - type: string - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - labels: - items: - type: string - type: array - project: - type: string - pullRequestState: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - project - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - scmProvider: - properties: - awsCodeCommit: - properties: - allBranches: - type: boolean - region: - type: string - role: - type: string - tagFilters: - items: - properties: - key: - type: string - value: - type: string - required: - - key - type: object - type: array - type: object - azureDevOps: - properties: - accessTokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - allBranches: - type: boolean - api: - type: string - organization: - type: string - teamProject: - type: string - required: - - accessTokenRef - - organization - - teamProject - type: object - bitbucket: - properties: - allBranches: - type: boolean - appPasswordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - owner: - type: string - user: - type: string - required: - - appPasswordRef - - owner - - user - type: object - bitbucketServer: - properties: - allBranches: - type: boolean - api: - type: string - basicAuth: - properties: - passwordRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - username: - type: string - required: - - passwordRef - - username - type: object - bearerToken: - properties: - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - tokenRef - type: object - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - insecure: - type: boolean - project: - type: string - required: - - api - - project - type: object - cloneProtocol: - type: string - filters: - items: - properties: - branchMatch: - type: string - labelMatch: - type: string - pathsDoNotExist: - items: - type: string - type: array - pathsExist: - items: - type: string - type: array - repositoryMatch: - type: string - type: object - type: array - gitea: - properties: - allBranches: - type: boolean - api: - type: string - insecure: - type: boolean - owner: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - api - - owner - type: object - github: - properties: - allBranches: - type: boolean - api: - type: string - appSecretName: - type: string - organization: - type: string - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - required: - - organization - type: object - gitlab: - properties: - allBranches: - type: boolean - api: - type: string - caRef: - properties: - configMapName: - type: string - key: - type: string - required: - - configMapName - - key - type: object - group: - type: string - includeSharedProjects: - type: boolean - includeSubgroups: - type: boolean - insecure: - type: boolean - tokenRef: - properties: - key: - type: string - secretName: - type: string - required: - - key - - secretName - type: object - topic: - type: string - required: - - group - type: object - requeueAfterSeconds: - format: int64 - type: integer - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - values: - additionalProperties: - type: string - type: object - type: object - selector: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - key - - operator - type: object - type: array - x-kubernetes-list-type: atomic - matchLabels: - additionalProperties: - type: string - type: object - type: object - x-kubernetes-map-type: atomic - type: object - type: array - goTemplate: - type: boolean - goTemplateOptions: - items: - type: string - type: array - ignoreApplicationDifferences: - items: - properties: - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - name: - type: string - type: object - type: array - preservedFields: - properties: - annotations: - items: - type: string - type: array - labels: - items: - type: string - type: array - type: object - strategy: - properties: - deletionOrder: - type: string - rollingSync: - properties: - steps: - items: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - type: object - type: array - maxUpdate: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - type: object - type: array - type: object - type: - type: string - type: object - syncPolicy: - properties: - applicationsSync: - enum: - - create-only - - create-update - - create-delete - - sync - type: string - preserveResourcesOnDeletion: - type: boolean - type: object - template: - properties: - metadata: - properties: - annotations: - additionalProperties: - type: string - type: object - finalizers: - items: - type: string - type: array - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - type: object - spec: - properties: - destination: - properties: - name: - type: string - namespace: - type: string - server: - type: string - type: object - ignoreDifferences: - items: - properties: - group: - type: string - jqPathExpressions: - items: - type: string - type: array - jsonPointers: - items: - type: string - type: array - kind: - type: string - managedFieldsManagers: - items: - type: string - type: array - name: - type: string - namespace: - type: string - required: - - kind - type: object - type: array - info: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - project: - type: string - revisionHistoryLimit: - format: int64 - type: integer - source: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - sourceHydrator: - properties: - drySource: - properties: - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - repoURL: - type: string - targetRevision: - type: string - required: - - path - - repoURL - - targetRevision - type: object - hydrateTo: - properties: - targetBranch: - type: string - required: - - targetBranch - type: object - syncSource: - properties: - path: - minLength: 1 - pattern: ^.{2,}|[^./]$ - type: string - targetBranch: - type: string - required: - - path - - targetBranch - type: object - required: - - drySource - - syncSource - type: object - sources: - items: - properties: - chart: - type: string - directory: - properties: - exclude: - type: string - include: - type: string - jsonnet: - properties: - extVars: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - libs: - items: - type: string - type: array - tlas: - items: - properties: - code: - type: boolean - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - type: object - recurse: - type: boolean - type: object - helm: - properties: - apiVersions: - items: - type: string - type: array - fileParameters: - items: - properties: - name: - type: string - path: - type: string - type: object - type: array - ignoreMissingValueFiles: - type: boolean - kubeVersion: - type: string - namespace: - type: string - parameters: - items: - properties: - forceString: - type: boolean - name: - type: string - value: - type: string - type: object - type: array - passCredentials: - type: boolean - releaseName: - type: string - skipCrds: - type: boolean - skipSchemaValidation: - type: boolean - skipTests: - type: boolean - valueFiles: - items: - type: string - type: array - values: - type: string - valuesObject: - type: object - x-kubernetes-preserve-unknown-fields: true - version: - type: string - type: object - kustomize: - properties: - apiVersions: - items: - type: string - type: array - commonAnnotations: - additionalProperties: - type: string - type: object - commonAnnotationsEnvsubst: - type: boolean - commonLabels: - additionalProperties: - type: string - type: object - components: - items: - type: string - type: array - forceCommonAnnotations: - type: boolean - forceCommonLabels: - type: boolean - ignoreMissingComponents: - type: boolean - images: - items: - type: string - type: array - kubeVersion: - type: string - labelIncludeTemplates: - type: boolean - labelWithoutSelector: - type: boolean - namePrefix: - type: string - nameSuffix: - type: string - namespace: - type: string - patches: - items: - properties: - options: - additionalProperties: - type: boolean - type: object - patch: - type: string - path: - type: string - target: - properties: - annotationSelector: - type: string - group: - type: string - kind: - type: string - labelSelector: - type: string - name: - type: string - namespace: - type: string - version: - type: string - type: object - type: object - type: array - replicas: - items: - properties: - count: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - name: - type: string - required: - - count - - name - type: object - type: array - version: - type: string - type: object - name: - type: string - path: - type: string - plugin: - properties: - env: - items: - properties: - name: - type: string - value: - type: string - required: - - name - - value - type: object - type: array - name: - type: string - parameters: - items: - properties: - array: - items: - type: string - type: array - map: - additionalProperties: - type: string - type: object - name: - type: string - string: - type: string - type: object - type: array - type: object - ref: - type: string - repoURL: - type: string - targetRevision: - type: string - required: - - repoURL - type: object - type: array - syncPolicy: - properties: - automated: - properties: - allowEmpty: - type: boolean - enabled: - type: boolean - prune: - type: boolean - selfHeal: - type: boolean - type: object - managedNamespaceMetadata: - properties: - annotations: - additionalProperties: - type: string - type: object - labels: - additionalProperties: - type: string - type: object - type: object - retry: - properties: - backoff: - properties: - duration: - type: string - factor: - format: int64 - type: integer - maxDuration: - type: string - type: object - limit: - format: int64 - type: integer - refresh: - type: boolean - type: object - syncOptions: - items: - type: string - type: array - type: object - required: - - destination - - project - type: object - required: - - metadata - - spec - type: object - templatePatch: - type: string - required: - - generators - - template - type: object - status: - properties: - applicationStatus: - items: - properties: - application: - type: string - lastTransitionTime: - format: date-time - type: string - message: - type: string - status: - type: string - step: - type: string - targetRevisions: - items: - type: string - type: array - required: - - application - - message - - status - - step - - targetRevisions - type: object - type: array - conditions: - items: - properties: - lastTransitionTime: - format: date-time - type: string - message: - type: string - reason: - type: string - status: - type: string - type: - type: string - required: - - message - - reason - - status - - type - type: object - type: array - health: - properties: - lastTransitionTime: - format: date-time - type: string - message: - type: string - status: - type: string - type: object - resources: - items: - properties: - group: - type: string - health: - properties: - lastTransitionTime: - format: date-time - type: string - message: - type: string - status: - type: string - type: object - hook: - type: boolean - kind: - type: string - name: - type: string - namespace: - type: string - requiresDeletionConfirmation: - type: boolean - requiresPruning: - type: boolean - status: - type: string - syncWave: - format: int64 - type: integer - version: - type: string - type: object - type: array - resourcesCount: - format: int64 - type: integer - type: object - required: - - metadata - - spec - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - app.kubernetes.io/name: appprojects.argoproj.io - app.kubernetes.io/part-of: argocd - name: appprojects.argoproj.io -spec: - group: argoproj.io - names: - kind: AppProject - listKind: AppProjectList - plural: appprojects - shortNames: - - appproj - - appprojs - singular: appproject - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - AppProject provides a logical grouping of applications, providing controls for: - * where the apps may deploy to (cluster whitelist) - * what may be deployed (repository whitelist, resource whitelist/blacklist) - * who can access these applications (roles, OIDC group claims bindings) - * and what they can do (RBAC policies) - * automation access to these roles (JWT tokens) - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: AppProjectSpec is the specification of an AppProject - properties: - clusterResourceBlacklist: - description: ClusterResourceBlacklist contains list of blacklisted - cluster level resources - items: - description: ClusterResourceRestrictionItem is a cluster resource - that is restricted by the project's whitelist or blacklist - properties: - group: - type: string - kind: - type: string - name: - description: |- - Name is the name of the restricted resource. Glob patterns using Go's filepath.Match syntax are supported. - Unlike the group and kind fields, if no name is specified, all resources of the specified group/kind are matched. - type: string - required: - - group - - kind - type: object - type: array - clusterResourceWhitelist: - description: ClusterResourceWhitelist contains list of whitelisted - cluster level resources - items: - description: ClusterResourceRestrictionItem is a cluster resource - that is restricted by the project's whitelist or blacklist - properties: - group: - type: string - kind: - type: string - name: - description: |- - Name is the name of the restricted resource. Glob patterns using Go's filepath.Match syntax are supported. - Unlike the group and kind fields, if no name is specified, all resources of the specified group/kind are matched. - type: string - required: - - group - - kind - type: object - type: array - description: - description: Description contains optional project description - maxLength: 255 - type: string - destinationServiceAccounts: - description: DestinationServiceAccounts holds information about the - service accounts to be impersonated for the application sync operation - for each destination. - items: - description: ApplicationDestinationServiceAccount holds information - about the service account to be impersonated for the application - sync operation. - properties: - defaultServiceAccount: - description: DefaultServiceAccount to be used for impersonation - during the sync operation - type: string - namespace: - description: Namespace specifies the target namespace for the - application's resources. - type: string - server: - description: Server specifies the URL of the target cluster's - Kubernetes control plane API. - type: string - required: - - defaultServiceAccount - - server - type: object - type: array - destinations: - description: Destinations contains list of destinations available - for deployment - items: - description: ApplicationDestination holds information about the - application's destination - properties: - name: - description: Name is an alternate way of specifying the target - cluster by its symbolic name. This must be set if Server is - not set. - type: string - namespace: - description: |- - Namespace specifies the target namespace for the application's resources. - The namespace will only be set for namespace-scoped resources that have not set a value for .metadata.namespace - type: string - server: - description: Server specifies the URL of the target cluster's - Kubernetes control plane API. This must be set if Name is - not set. - type: string - type: object - type: array - namespaceResourceBlacklist: - description: NamespaceResourceBlacklist contains list of blacklisted - namespace level resources - items: - description: |- - GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying - concepts during lookup stages without having partially valid types - properties: - group: - type: string - kind: - type: string - required: - - group - - kind - type: object - type: array - namespaceResourceWhitelist: - description: NamespaceResourceWhitelist contains list of whitelisted - namespace level resources - items: - description: |- - GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying - concepts during lookup stages without having partially valid types - properties: - group: - type: string - kind: - type: string - required: - - group - - kind - type: object - type: array - orphanedResources: - description: OrphanedResources specifies if controller should monitor - orphaned resources of apps in this project - properties: - ignore: - description: Ignore contains a list of resources that are to be - excluded from orphaned resources monitoring - items: - description: OrphanedResourceKey is a reference to a resource - to be ignored from - properties: - group: - type: string - kind: - type: string - name: - type: string - type: object - type: array - warn: - description: Warn indicates if warning condition should be created - for apps which have orphaned resources - type: boolean - type: object - permitOnlyProjectScopedClusters: - description: PermitOnlyProjectScopedClusters determines whether destinations - can only reference clusters which are project-scoped - type: boolean - roles: - description: Roles are user defined RBAC roles associated with this - project - items: - description: ProjectRole represents a role that has access to a - project - properties: - description: - description: Description is a description of the role - type: string - groups: - description: Groups are a list of OIDC group claims bound to - this role - items: - type: string - type: array - jwtTokens: - description: JWTTokens are a list of generated JWT tokens bound - to this role - items: - description: JWTToken holds the issuedAt and expiresAt values - of a token - properties: - exp: - format: int64 - type: integer - iat: - format: int64 - type: integer - id: - type: string - required: - - iat - type: object - type: array - name: - description: Name is a name for this role - type: string - policies: - description: Policies Stores a list of casbin formatted strings - that define access policies for the role in the project - items: - type: string - type: array - required: - - name - type: object - type: array - signatureKeys: - description: SignatureKeys contains a list of PGP key IDs that commits - in Git must be signed with in order to be allowed for sync - items: - description: SignatureKey is the specification of a key required - to verify commit signatures with - properties: - keyID: - description: The ID of the key in hexadecimal notation - type: string - required: - - keyID - type: object - type: array - sourceNamespaces: - description: SourceNamespaces defines the namespaces application resources - are allowed to be created in - items: - type: string - type: array - sourceRepos: - description: SourceRepos contains list of repository URLs which can - be used for deployment - items: - type: string - type: array - syncWindows: - description: SyncWindows controls when syncs can be run for apps in - this project - items: - description: SyncWindow contains the kind, time, duration and attributes - that are used to assign the syncWindows to apps - properties: - andOperator: - description: UseAndOperator use AND operator for matching applications, - namespaces and clusters instead of the default OR operator - type: boolean - applications: - description: Applications contains a list of applications that - the window will apply to - items: - type: string - type: array - clusters: - description: Clusters contains a list of clusters that the window - will apply to - items: - type: string - type: array - description: - description: Description of the sync that will be applied to - the schedule, can be used to add any information such as a - ticket number for example - type: string - duration: - description: Duration is the amount of time the sync window - will be open - type: string - kind: - description: Kind defines if the window allows or blocks syncs - type: string - manualSync: - description: ManualSync enables manual syncs when they would - otherwise be blocked - type: boolean - namespaces: - description: Namespaces contains a list of namespaces that the - window will apply to - items: - type: string - type: array - schedule: - description: Schedule is the time the window will begin, specified - in cron format - type: string - timeZone: - description: TimeZone of the sync that will be applied to the - schedule - type: string - type: object - type: array - type: object - status: - description: AppProjectStatus contains status information for AppProject - CRs - properties: - jwtTokensByRole: - additionalProperties: - description: JWTTokens represents a list of JWT tokens - properties: - items: - items: - description: JWTToken holds the issuedAt and expiresAt values - of a token - properties: - exp: - format: int64 - type: integer - iat: - format: int64 - type: integer - id: - type: string - required: - - iat - type: object - type: array - type: object - description: JWTTokensByRole contains a list of JWT tokens issued - for a given role - type: object - type: object - required: - - metadata - - spec - type: object - served: true - storage: true ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-controller ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: redis - app.kubernetes.io/name: argocd-redis - app.kubernetes.io/part-of: argocd - name: argocd-redis ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: repo-server - app.kubernetes.io/name: argocd-repo-server - app.kubernetes.io/part-of: argocd - name: argocd-repo-server ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller -rules: -- apiGroups: - - "" - resources: - - secrets - - configmaps - verbs: - - get - - list - - watch -- apiGroups: - - argoproj.io - resources: - - applications - - applicationsets - - appprojects - verbs: - - create - - get - - list - - watch - - update - - patch - - delete -- apiGroups: - - "" - resources: - - events - verbs: - - create - - list -- apiGroups: - - apps - resources: - - deployments - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller -rules: -- apiGroups: - - argoproj.io - resources: - - applications - - applicationsets - - applicationsets/finalizers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - argoproj.io - resources: - - appprojects - verbs: - - get - - list - - watch -- apiGroups: - - argoproj.io - resources: - - applicationsets/status - verbs: - - get - - patch - - update -- apiGroups: - - "" - resources: - - events - verbs: - - create - - get - - list - - patch - - watch -- apiGroups: - - "" - resources: - - secrets - - configmaps - verbs: - - get - - list - - watch -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create -- apiGroups: - - coordination.k8s.io - resourceNames: - - 58ac56fa.applicationsets.argoproj.io - resources: - - leases - verbs: - - get - - update - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-controller -rules: -- apiGroups: - - argoproj.io - resources: - - applications - - appprojects - verbs: - - get - - list - - watch - - update - - patch -- apiGroups: - - "" - resources: - - configmaps - - secrets - verbs: - - list - - watch -- apiGroups: - - "" - resourceNames: - - argocd-notifications-cm - resources: - - configmaps - verbs: - - get -- apiGroups: - - "" - resourceNames: - - argocd-notifications-secret - resources: - - secrets - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/component: redis - app.kubernetes.io/name: argocd-redis - app.kubernetes.io/part-of: argocd - name: argocd-redis -rules: -- apiGroups: - - "" - resourceNames: - - argocd-redis - resources: - - secrets - verbs: - - get -- apiGroups: - - "" - resources: - - secrets - verbs: - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server -rules: -- apiGroups: - - "" - resources: - - secrets - - configmaps - verbs: - - create - - get - - list - - watch - - update - - patch - - delete -- apiGroups: - - argoproj.io - resources: - - applications - - appprojects - - applicationsets - verbs: - - create - - get - - list - - watch - - update - - delete - - patch -- apiGroups: - - "" - resources: - - events - verbs: - - create - - list ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller -rules: -- apiGroups: - - '*' - resources: - - '*' - verbs: - - '*' -- nonResourceURLs: - - '*' - verbs: - - '*' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller -rules: -- apiGroups: - - argoproj.io - resources: - - applications - - applicationsets - - applicationsets/finalizers - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - argoproj.io - resources: - - appprojects - verbs: - - get - - list - - watch -- apiGroups: - - argoproj.io - resources: - - applicationsets/status - verbs: - - get - - patch - - update -- apiGroups: - - "" - resources: - - events - verbs: - - create - - get - - list - - patch - - watch -- apiGroups: - - "" - resources: - - secrets - - configmaps - verbs: - - get - - list - - watch -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create -- apiGroups: - - coordination.k8s.io - resourceNames: - - 58ac56fa.applicationsets.argoproj.io - resources: - - leases - verbs: - - get - - update - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server -rules: -- apiGroups: - - '*' - resources: - - '*' - verbs: - - delete - - get - - patch -- apiGroups: - - "" - resources: - - events - verbs: - - list -- apiGroups: - - "" - resources: - - pods - - pods/log - verbs: - - get -- apiGroups: - - argoproj.io - resources: - - applications - - applicationsets - verbs: - - get - - list - - watch -- apiGroups: - - batch - resources: - - jobs - verbs: - - create -- apiGroups: - - argoproj.io - resources: - - workflows - verbs: - - create ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: argocd-application-controller -subjects: -- kind: ServiceAccount - name: argocd-application-controller ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: argocd-applicationset-controller -subjects: -- kind: ServiceAccount - name: argocd-applicationset-controller ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: argocd-notifications-controller -subjects: -- kind: ServiceAccount - name: argocd-notifications-controller ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/component: redis - app.kubernetes.io/name: argocd-redis - app.kubernetes.io/part-of: argocd - name: argocd-redis -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: argocd-redis -subjects: -- kind: ServiceAccount - name: argocd-redis ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: argocd-server -subjects: -- kind: ServiceAccount - name: argocd-server ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: argocd-application-controller -subjects: -- kind: ServiceAccount - name: argocd-application-controller - namespace: argocd ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: argocd-applicationset-controller -subjects: -- kind: ServiceAccount - name: argocd-applicationset-controller - namespace: argocd ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: argocd-server -subjects: -- kind: ServiceAccount - name: argocd-server - namespace: argocd ---- -apiVersion: v1 -data: - resource.customizations.ignoreResourceUpdates.ConfigMap: | - jqPathExpressions: - # Ignore the cluster-autoscaler status - - '.metadata.annotations."cluster-autoscaler.kubernetes.io/last-updated"' - # Ignore the annotation of the legacy Leases election - - '.metadata.annotations."control-plane.alpha.kubernetes.io/leader"' - resource.customizations.ignoreResourceUpdates.Endpoints: | - jsonPointers: - - /metadata - - /subsets - resource.customizations.ignoreResourceUpdates.all: | - jsonPointers: - - /status - resource.customizations.ignoreResourceUpdates.apps_ReplicaSet: | - jqPathExpressions: - - '.metadata.annotations."deployment.kubernetes.io/desired-replicas"' - - '.metadata.annotations."deployment.kubernetes.io/max-replicas"' - - '.metadata.annotations."rollout.argoproj.io/desired-replicas"' - resource.customizations.ignoreResourceUpdates.argoproj.io_Application: | - jqPathExpressions: - - '.metadata.annotations."notified.notifications.argoproj.io"' - - '.metadata.annotations."argocd.argoproj.io/refresh"' - - '.metadata.annotations."argocd.argoproj.io/hydrate"' - - '.operation' - resource.customizations.ignoreResourceUpdates.argoproj.io_Rollout: | - jqPathExpressions: - - '.metadata.annotations."notified.notifications.argoproj.io"' - resource.customizations.ignoreResourceUpdates.autoscaling_HorizontalPodAutoscaler: | - jqPathExpressions: - - '.metadata.annotations."autoscaling.alpha.kubernetes.io/behavior"' - - '.metadata.annotations."autoscaling.alpha.kubernetes.io/conditions"' - - '.metadata.annotations."autoscaling.alpha.kubernetes.io/metrics"' - - '.metadata.annotations."autoscaling.alpha.kubernetes.io/current-metrics"' - resource.customizations.ignoreResourceUpdates.discovery.k8s.io_EndpointSlice: | - jsonPointers: - - /metadata - - /endpoints - - /ports - resource.exclusions: | - ### Network resources created by the Kubernetes control plane and excluded to reduce the number of watched events and UI clutter - - apiGroups: - - '' - - discovery.k8s.io - kinds: - - Endpoints - - EndpointSlice - ### Internal Kubernetes resources excluded reduce the number of watched events - - apiGroups: - - coordination.k8s.io - kinds: - - Lease - ### Internal Kubernetes Authz/Authn resources excluded reduce the number of watched events - - apiGroups: - - authentication.k8s.io - - authorization.k8s.io - kinds: - - SelfSubjectReview - - TokenReview - - LocalSubjectAccessReview - - SelfSubjectAccessReview - - SelfSubjectRulesReview - - SubjectAccessReview - ### Intermediate Certificate Request excluded reduce the number of watched events - - apiGroups: - - certificates.k8s.io - kinds: - - CertificateSigningRequest - - apiGroups: - - cert-manager.io - kinds: - - CertificateRequest - ### Cilium internal resources excluded reduce the number of watched events and UI Clutter - - apiGroups: - - cilium.io - kinds: - - CiliumIdentity - - CiliumEndpoint - - CiliumEndpointSlice - ### Kyverno intermediate and reporting resources excluded reduce the number of watched events and improve performance - - apiGroups: - - kyverno.io - - reports.kyverno.io - - wgpolicyk8s.io - kinds: - - PolicyReport - - ClusterPolicyReport - - EphemeralReport - - ClusterEphemeralReport - - AdmissionReport - - ClusterAdmissionReport - - BackgroundScanReport - - ClusterBackgroundScanReport - - UpdateRequest - url: https://argo.basicstack.de - oidc.config: | - name: Pocket ID - issuer: https://auth.basicstack.de - clientID: $oidc.pocketid.clientId - clientSecret: $oidc.pocketid.clientSecret - requestedScopes: - - openid - - profile - - email - - groups - requestedIDTokenClaims: - groups: - essential: true -kind: ConfigMap -metadata: - name: argocd-cm - labels: - app.kubernetes.io/name: argocd-cm - app.kubernetes.io/part-of: argocd ---- -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/name: argocd-cmd-params-cm - app.kubernetes.io/part-of: argocd - name: argocd-cmd-params-cm ---- -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/name: argocd-gpg-keys-cm - app.kubernetes.io/part-of: argocd - name: argocd-gpg-keys-cm ---- -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-cm ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: argocd-rbac-cm - labels: - app.kubernetes.io/name: argocd-rbac-cm - app.kubernetes.io/part-of: argocd -data: - policy.default: role:readonly - policy.csv: | - # Map argo_admins group to admin role - g, argo_admins, role:admin - - # Grant admin role full permissions - p, role:admin, applications, *, */*, allow - p, role:admin, clusters, *, *, allow - p, role:admin, repositories, *, *, allow - p, role:admin, projects, *, *, allow - p, role:admin, accounts, *, *, allow - p, role:admin, gpgkeys, *, *, allow - p, role:admin, certificates, *, *, allow - p, role:admin, exec, *, *, allow ---- -apiVersion: v1 -data: - ssh_known_hosts: | - # This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT - [ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= - [ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl - [ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= - bitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE= - bitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO - bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M= - github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= - github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl - github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= - gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY= - gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf - gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9 - ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H - vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H - forgejo.forgejo.svc.cluster.local ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnFlDTGVmri7geuOfZ1UvHzCRi1U5BqCq+9MwkrbGkZCmBmUt3ek95JBZzurLvR/0rE624B9tB1AvSJIEIQ1W1YXD4ydiDaHX6J0sip6lEfqhREx0y91C15zHgi9jN0UKidse1g0xlLHpwePUmz8/2BJcLHJuwbZSdUu7+uhDIYtIJ5+3trX3IABNeluRA5TkspPAC0ViEaz4saWgWQWjKL8dsb3jIR94DiWAVpQnhaCBEILkIStJrmWl0O5B10Jr3KWy18szr9UVd8edCkoEXriCR8gx69jHdmuem5WlZPGvsK5Adf0mXE8S3rdyHqWOkDRs6Wlwd9p/1nDY8c8wtE3vqhefCpt1BwpTys9PMmgUfG0at/W+NdvalRqgQz26Bso8Tf7hcfyA/B69U2pvjA0tbgdIGJ5kLeCzpz9kpCYn8wSIuNIJ86BpyfnjRYFCYJVc6Ls86i8j3fEZAjEX7bmbeDBHQnyyH+rjq+Llo8aUd2Uf7HoSA93EjeuWq/Ta+YbWEWrp9Mrd48jnypxHXsiwDzqkm+YGQbXHfasiarxji/eQ5UMMG8hCxpLp1lJLhGN2th4eCLkpwchFr5jZGWgyZCth1WzQGj7NHvDTxKRU6n4MfsEX1B6GF0D60qtM5vZynpmO902mkn6wtxo+pCkDMroj668a62zw3rSS5Bw== -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/name: argocd-ssh-known-hosts-cm - app.kubernetes.io/part-of: argocd - name: argocd-ssh-known-hosts-cm ---- -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - app.kubernetes.io/name: argocd-tls-certs-cm - app.kubernetes.io/part-of: argocd - name: argocd-tls-certs-cm ---- -apiVersion: v1 -kind: Secret -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-secret -type: Opaque ---- -apiVersion: v1 -kind: Secret -metadata: - labels: - app.kubernetes.io/name: argocd-secret - app.kubernetes.io/part-of: argocd - name: argocd-secret -type: Opaque ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller -spec: - ports: - - name: webhook - port: 7000 - protocol: TCP - targetPort: webhook - - name: metrics - port: 8080 - protocol: TCP - targetPort: metrics - selector: - app.kubernetes.io/name: argocd-applicationset-controller ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: metrics - app.kubernetes.io/name: argocd-metrics - app.kubernetes.io/part-of: argocd - name: argocd-metrics -spec: - ports: - - name: metrics - port: 8082 - protocol: TCP - targetPort: 8082 - selector: - app.kubernetes.io/name: argocd-application-controller ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller-metrics - app.kubernetes.io/part-of: argocd - name: argocd-notifications-controller-metrics -spec: - ports: - - name: metrics - port: 9001 - protocol: TCP - targetPort: 9001 - selector: - app.kubernetes.io/name: argocd-notifications-controller ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: redis - app.kubernetes.io/name: argocd-redis - app.kubernetes.io/part-of: argocd - name: argocd-redis -spec: - ports: - - name: tcp-redis - port: 6379 - targetPort: 6379 - selector: - app.kubernetes.io/name: argocd-redis ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: repo-server - app.kubernetes.io/name: argocd-repo-server - app.kubernetes.io/part-of: argocd - name: argocd-repo-server -spec: - ports: - - name: server - port: 8081 - protocol: TCP - targetPort: 8081 - - name: metrics - port: 8084 - protocol: TCP - targetPort: 8084 - selector: - app.kubernetes.io/name: argocd-repo-server ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: 8080 - - name: https - port: 443 - protocol: TCP - targetPort: 8080 - selector: - app.kubernetes.io/name: argocd-server ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server-metrics - app.kubernetes.io/part-of: argocd - name: argocd-server-metrics -spec: - ports: - - name: metrics - port: 8083 - protocol: TCP - targetPort: 8083 - selector: - app.kubernetes.io/name: argocd-server ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller -spec: - selector: - matchLabels: - app.kubernetes.io/name: argocd-applicationset-controller - template: - metadata: - labels: - app.kubernetes.io/name: argocd-applicationset-controller - spec: - containers: - - args: - - /usr/local/bin/argocd-applicationset-controller - env: - - name: GRPC_ENABLE_TXT_SERVICE_CONFIG - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.grpc.enable.txt.service.config - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_ANNOTATIONS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.global.preserved.annotations - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_GLOBAL_PRESERVED_LABELS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.global.preserved.labels - name: argocd-cmd-params-cm - optional: true - - name: NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_LEADER_ELECTION - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.leader.election - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER - valueFrom: - configMapKeyRef: - key: repo.server - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_POLICY - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.policy - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_POLICY_OVERRIDE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.policy.override - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_DEBUG - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.debug - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_LOGFORMAT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.log.format - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_LOGLEVEL - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.log.level - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_LOG_FORMAT_TIMESTAMP - valueFrom: - configMapKeyRef: - key: log.format.timestamp - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_QPS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.client.qps - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_BURST - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.client.burst - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.client.max.idle.connections - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_TIMEOUT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.tcp.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_KEEPALIVE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.tcp.keepalive - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TLS_HANDSHAKE_TIMEOUT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.tls.handshake.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_IDLE_TIMEOUT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.k8s.tcp.idle.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_DRY_RUN - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.dryrun - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_GIT_MODULES_ENABLED - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.git.submodule - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.progressive.syncs - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_TOKENREF_STRICT_MODE - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.tokenref.strict.mode - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.new.git.file.globbing - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_PLAINTEXT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.repo.server.plaintext - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_STRICT_TLS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.repo.server.strict.tls - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.repo.server.timeout.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_CONCURRENT_RECONCILIATIONS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.concurrent.reconciliations.max - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_NAMESPACES - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.namespaces - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.scm.root.ca.path - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.allowed.scm.providers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_SCM_PROVIDERS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.scm.providers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_GITHUB_API_METRICS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.github.api.metrics - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_WEBHOOK_PARALLELISM_LIMIT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.webhook.parallelism.limit - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_REQUEUE_AFTER - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.requeue.after - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_MAX_RESOURCES_STATUS_COUNT - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.status.max.resources.count - name: argocd-cmd-params-cm - optional: true - image: quay.io/argoproj/argocd:v3.4.5 - imagePullPolicy: Always - name: argocd-applicationset-controller - ports: - - containerPort: 7000 - name: webhook - - containerPort: 8080 - name: metrics - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /app/config/ssh - name: ssh-known-hosts - - mountPath: /app/config/tls - name: tls-certs - - mountPath: /app/config/gpg/source - name: gpg-keys - - mountPath: /app/config/gpg/keys - name: gpg-keyring - - mountPath: /tmp - name: tmp - - mountPath: /app/config/reposerver/tls - name: argocd-repo-server-tls - - mountPath: /home/argocd/params - name: argocd-cmd-params-cm - nodeSelector: - kubernetes.io/os: linux - serviceAccountName: argocd-applicationset-controller - volumes: - - configMap: - name: argocd-ssh-known-hosts-cm - name: ssh-known-hosts - - configMap: - name: argocd-tls-certs-cm - name: tls-certs - - configMap: - name: argocd-gpg-keys-cm - name: gpg-keys - - emptyDir: {} - name: gpg-keyring - - emptyDir: {} - name: tmp - - name: argocd-repo-server-tls - secret: - items: - - key: tls.crt - path: tls.crt - - key: tls.key - path: tls.key - - key: ca.crt - path: ca.crt - optional: true - secretName: argocd-repo-server-tls - - configMap: - items: - - key: applicationsetcontroller.profile.enabled - path: profiler.enabled - name: argocd-cmd-params-cm - optional: true - name: argocd-cmd-params-cm ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-controller -spec: - selector: - matchLabels: - app.kubernetes.io/name: argocd-notifications-controller - strategy: - type: Recreate - template: - metadata: - labels: - app.kubernetes.io/name: argocd-notifications-controller - spec: - containers: - - args: - - /usr/local/bin/argocd-notifications - env: - - name: ARGOCD_NOTIFICATIONS_CONTROLLER_LOGFORMAT - valueFrom: - configMapKeyRef: - key: notificationscontroller.log.format - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_NOTIFICATIONS_CONTROLLER_LOGLEVEL - valueFrom: - configMapKeyRef: - key: notificationscontroller.log.level - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_NOTIFICATION_CONTROLLER_PROCESSORS_COUNT - valueFrom: - configMapKeyRef: - key: notificationscontroller.processors.count - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_LOG_FORMAT_TIMESTAMP - valueFrom: - configMapKeyRef: - key: log.format.timestamp - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_NAMESPACES - valueFrom: - configMapKeyRef: - key: application.namespaces - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED - valueFrom: - configMapKeyRef: - key: notificationscontroller.selfservice.enabled - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_NOTIFICATION_CONTROLLER_REPO_SERVER_PLAINTEXT - valueFrom: - configMapKeyRef: - key: notificationscontroller.repo.server.plaintext - name: argocd-cmd-params-cm - optional: true - image: quay.io/argoproj/argocd:v3.4.5 - imagePullPolicy: Always - livenessProbe: - tcpSocket: - port: 9001 - name: argocd-notifications-controller - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - volumeMounts: - - mountPath: /app/config/tls - name: tls-certs - - mountPath: /app/config/reposerver/tls - name: argocd-repo-server-tls - workingDir: /app - nodeSelector: - kubernetes.io/os: linux - securityContext: - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - serviceAccountName: argocd-notifications-controller - volumes: - - configMap: - name: argocd-tls-certs-cm - name: tls-certs - - name: argocd-repo-server-tls - secret: - items: - - key: tls.crt - path: tls.crt - - key: tls.key - path: tls.key - - key: ca.crt - path: ca.crt - optional: true - secretName: argocd-repo-server-tls ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/component: redis - app.kubernetes.io/name: argocd-redis - app.kubernetes.io/part-of: argocd - name: argocd-redis -spec: - selector: - matchLabels: - app.kubernetes.io/name: argocd-redis - template: - metadata: - labels: - app.kubernetes.io/name: argocd-redis - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/name: argocd-redis - topologyKey: kubernetes.io/hostname - weight: 100 - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/part-of: argocd - topologyKey: kubernetes.io/hostname - weight: 5 - containers: - - args: - - --save - - "" - - --appendonly - - "no" - - --requirepass $(REDIS_PASSWORD) - env: - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - key: auth - name: argocd-redis - image: public.ecr.aws/docker/library/redis:8.2.3-alpine - imagePullPolicy: Always - name: redis - ports: - - containerPort: 6379 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - initContainers: - - command: - - argocd - - admin - - redis-initial-password - image: quay.io/argoproj/argocd:v3.4.5 - imagePullPolicy: IfNotPresent - name: secret-init - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - nodeSelector: - kubernetes.io/os: linux - securityContext: - runAsNonRoot: true - runAsUser: 999 - seccompProfile: - type: RuntimeDefault - serviceAccountName: argocd-redis ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/component: repo-server - app.kubernetes.io/name: argocd-repo-server - app.kubernetes.io/part-of: argocd - name: argocd-repo-server -spec: - selector: - matchLabels: - app.kubernetes.io/name: argocd-repo-server - template: - metadata: - labels: - app.kubernetes.io/name: argocd-repo-server - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/name: argocd-repo-server - topologyKey: kubernetes.io/hostname - weight: 100 - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/part-of: argocd - topologyKey: kubernetes.io/hostname - weight: 5 - automountServiceAccountToken: false - containers: - - args: - - /usr/local/bin/argocd-repo-server - env: - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - key: auth - name: argocd-redis - - name: GRPC_ENABLE_TXT_SERVICE_CONFIG - valueFrom: - configMapKeyRef: - key: reposerver.grpc.enable.txt.service.config - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_RECONCILIATION_TIMEOUT - valueFrom: - configMapKeyRef: - key: timeout.reconciliation - name: argocd-cm - optional: true - - name: ARGOCD_REPO_SERVER_LOGFORMAT - valueFrom: - configMapKeyRef: - key: reposerver.log.format - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_LOGLEVEL - valueFrom: - configMapKeyRef: - key: reposerver.log.level - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_LOG_FORMAT_TIMESTAMP - valueFrom: - configMapKeyRef: - key: log.format.timestamp - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_PARALLELISM_LIMIT - valueFrom: - configMapKeyRef: - key: reposerver.parallelism.limit - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_LISTEN_ADDRESS - valueFrom: - configMapKeyRef: - key: reposerver.listen.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_LISTEN_METRICS_ADDRESS - valueFrom: - configMapKeyRef: - key: reposerver.metrics.listen.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_DISABLE_TLS - valueFrom: - configMapKeyRef: - key: reposerver.disable.tls - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_TLS_MIN_VERSION - valueFrom: - configMapKeyRef: - key: reposerver.tls.minversion - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_TLS_MAX_VERSION - valueFrom: - configMapKeyRef: - key: reposerver.tls.maxversion - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_TLS_CIPHERS - valueFrom: - configMapKeyRef: - key: reposerver.tls.ciphers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: reposerver.repo.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: REDIS_SERVER - valueFrom: - configMapKeyRef: - key: redis.server - name: argocd-cmd-params-cm - optional: true - - name: REDIS_COMPRESSION - valueFrom: - configMapKeyRef: - key: redis.compression - name: argocd-cmd-params-cm - optional: true - - name: REDISDB - valueFrom: - configMapKeyRef: - key: redis.db - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_DEFAULT_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: reposerver.default.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_OTLP_ADDRESS - valueFrom: - configMapKeyRef: - key: otlp.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_OTLP_INSECURE - valueFrom: - configMapKeyRef: - key: otlp.insecure - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_OTLP_HEADERS - valueFrom: - configMapKeyRef: - key: otlp.headers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_OTLP_ATTRS - valueFrom: - configMapKeyRef: - key: otlp.attrs - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_MAX_COMBINED_DIRECTORY_MANIFESTS_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.max.combined.directory.manifests.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS - valueFrom: - configMapKeyRef: - key: reposerver.plugin.tar.exclusions - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_PLUGIN_USE_MANIFEST_GENERATE_PATHS - valueFrom: - configMapKeyRef: - key: reposerver.plugin.use.manifest.generate.paths - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_ALLOW_OUT_OF_BOUNDS_SYMLINKS - valueFrom: - configMapKeyRef: - key: reposerver.allow.oob.symlinks - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.streamed.manifest.max.tar.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.streamed.manifest.max.extracted.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.helm.manifest.max.extracted.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.disable.helm.manifest.max.extracted.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_OCI_MANIFEST_MAX_EXTRACTED_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.oci.manifest.max.extracted.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_DISABLE_OCI_MANIFEST_MAX_EXTRACTED_SIZE - valueFrom: - configMapKeyRef: - key: reposerver.disable.oci.manifest.max.extracted.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_OCI_LAYER_MEDIA_TYPES - valueFrom: - configMapKeyRef: - key: reposerver.oci.layer.media.types - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REVISION_CACHE_LOCK_TIMEOUT - valueFrom: - configMapKeyRef: - key: reposerver.revision.cache.lock.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_GIT_MODULES_ENABLED - valueFrom: - configMapKeyRef: - key: reposerver.enable.git.submodule - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_GIT_LS_REMOTE_PARALLELISM_LIMIT - valueFrom: - configMapKeyRef: - key: reposerver.git.lsremote.parallelism.limit - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_GIT_REQUEST_TIMEOUT - valueFrom: - configMapKeyRef: - key: reposerver.git.request.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_ENABLE_BUILTIN_GIT_CONFIG - valueFrom: - configMapKeyRef: - key: reposerver.enable.builtin.git.config - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_GRPC_MAX_SIZE_MB - valueFrom: - configMapKeyRef: - key: reposerver.grpc.max.size - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_REPO_SERVER_INCLUDE_HIDDEN_DIRECTORIES - valueFrom: - configMapKeyRef: - key: reposerver.include.hidden.directories - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_HELM_USER_AGENT - valueFrom: - configMapKeyRef: - key: reposerver.helm.user.agent - name: argocd-cmd-params-cm - optional: true - - name: HELM_CACHE_HOME - value: /helm-working-dir - - name: HELM_CONFIG_HOME - value: /helm-working-dir - - name: HELM_DATA_HOME - value: /helm-working-dir - image: quay.io/argoproj/argocd:v3.4.5 - imagePullPolicy: Always - livenessProbe: - failureThreshold: 3 - httpGet: - path: /healthz?full=true - port: 8084 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 5 - name: argocd-repo-server - ports: - - containerPort: 8081 - - containerPort: 8084 - readinessProbe: - httpGet: - path: /healthz - port: 8084 - initialDelaySeconds: 5 - periodSeconds: 10 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /app/config/ssh - name: ssh-known-hosts - - mountPath: /app/config/tls - name: tls-certs - - mountPath: /app/config/gpg/source - name: gpg-keys - - mountPath: /app/config/gpg/keys - name: gpg-keyring - - mountPath: /app/config/reposerver/tls - name: argocd-repo-server-tls - - mountPath: /tmp - name: tmp - - mountPath: /helm-working-dir - name: helm-working-dir - - mountPath: /home/argocd/cmp-server/plugins - name: plugins - initContainers: - - args: - - /bin/cp /usr/local/bin/argocd /var/run/argocd/argocd && /bin/ln -sf /var/run/argocd/argocd - /var/run/argocd/argocd-cmp-server - command: - - sh - - -c - image: quay.io/argoproj/argocd:v3.4.5 - name: copyutil - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /var/run/argocd - name: var-files - nodeSelector: - kubernetes.io/os: linux - serviceAccountName: argocd-repo-server - volumes: - - configMap: - name: argocd-ssh-known-hosts-cm - name: ssh-known-hosts - - configMap: - name: argocd-tls-certs-cm - name: tls-certs - - configMap: - name: argocd-gpg-keys-cm - name: gpg-keys - - emptyDir: {} - name: gpg-keyring - - emptyDir: {} - name: tmp - - emptyDir: {} - name: helm-working-dir - - name: argocd-repo-server-tls - secret: - items: - - key: tls.crt - path: tls.crt - - key: tls.key - path: tls.key - - key: ca.crt - path: ca.crt - optional: true - secretName: argocd-repo-server-tls - - emptyDir: {} - name: var-files - - emptyDir: {} - name: plugins - - configMap: - items: - - key: reposerver.profile.enabled - path: profiler.enabled - name: argocd-cmd-params-cm - optional: true - name: argocd-cmd-params-cm ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server -spec: - selector: - matchLabels: - app.kubernetes.io/name: argocd-server - template: - metadata: - labels: - app.kubernetes.io/name: argocd-server - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/name: argocd-server - topologyKey: kubernetes.io/hostname - weight: 100 - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/part-of: argocd - topologyKey: kubernetes.io/hostname - weight: 5 - containers: - - args: - - /usr/local/bin/argocd-server - - --insecure - env: - - name: OIDC_POCKETID_CLIENTID - valueFrom: - secretKeyRef: - name: argocd-oidc-secret - key: oidc.pocketid.clientId - - name: OIDC_POCKETID_CLIENTSECRET - valueFrom: - secretKeyRef: - name: argocd-oidc-secret - key: oidc.pocketid.clientSecret - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - key: auth - name: argocd-redis - - name: GRPC_ENABLE_TXT_SERVICE_CONFIG - valueFrom: - configMapKeyRef: - key: server.grpc.enable.txt.service.config - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_INSECURE - valueFrom: - configMapKeyRef: - key: server.insecure - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_BASEHREF - valueFrom: - configMapKeyRef: - key: server.basehref - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_ROOTPATH - valueFrom: - configMapKeyRef: - key: server.rootpath - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_LOGFORMAT - valueFrom: - configMapKeyRef: - key: server.log.format - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_LOG_LEVEL - valueFrom: - configMapKeyRef: - key: server.log.level - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_QPS - valueFrom: - configMapKeyRef: - key: server.k8s.client.qps - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_BURST - valueFrom: - configMapKeyRef: - key: server.k8s.client.burst - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS - valueFrom: - configMapKeyRef: - key: server.k8s.client.max.idle.connections - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_TIMEOUT - valueFrom: - configMapKeyRef: - key: server.k8s.tcp.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_KEEPALIVE - valueFrom: - configMapKeyRef: - key: server.k8s.tcp.keepalive - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TLS_HANDSHAKE_TIMEOUT - valueFrom: - configMapKeyRef: - key: server.k8s.tls.handshake.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_IDLE_TIMEOUT - valueFrom: - configMapKeyRef: - key: server.k8s.tcp.idle.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_REPO_SERVER - valueFrom: - configMapKeyRef: - key: repo.server - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_DEX_SERVER - valueFrom: - configMapKeyRef: - key: server.dex.server - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_DISABLE_AUTH - valueFrom: - configMapKeyRef: - key: server.disable.auth - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_ENABLE_GZIP - valueFrom: - configMapKeyRef: - key: server.enable.gzip - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_REPO_SERVER_TIMEOUT_SECONDS - valueFrom: - configMapKeyRef: - key: server.repo.server.timeout.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_X_FRAME_OPTIONS - valueFrom: - configMapKeyRef: - key: server.x.frame.options - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_CONTENT_SECURITY_POLICY - valueFrom: - configMapKeyRef: - key: server.content.security.policy - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_REPO_SERVER_PLAINTEXT - valueFrom: - configMapKeyRef: - key: server.repo.server.plaintext - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_REPO_SERVER_STRICT_TLS - valueFrom: - configMapKeyRef: - key: server.repo.server.strict.tls - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_DEX_SERVER_PLAINTEXT - valueFrom: - configMapKeyRef: - key: server.dex.server.plaintext - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_DEX_SERVER_STRICT_TLS - valueFrom: - configMapKeyRef: - key: server.dex.server.strict.tls - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_TLS_MIN_VERSION - valueFrom: - configMapKeyRef: - key: server.tls.minversion - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_TLS_MAX_VERSION - valueFrom: - configMapKeyRef: - key: server.tls.maxversion - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_TLS_CIPHERS - valueFrom: - configMapKeyRef: - key: server.tls.ciphers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_CONNECTION_STATUS_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: server.connection.status.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_OIDC_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: server.oidc.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_STATIC_ASSETS - valueFrom: - configMapKeyRef: - key: server.staticassets - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APP_STATE_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: server.app.state.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: REDIS_SERVER - valueFrom: - configMapKeyRef: - key: redis.server - name: argocd-cmd-params-cm - optional: true - - name: REDIS_COMPRESSION - valueFrom: - configMapKeyRef: - key: redis.compression - name: argocd-cmd-params-cm - optional: true - - name: REDISDB - valueFrom: - configMapKeyRef: - key: redis.db - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_DEFAULT_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: server.default.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_MAX_COOKIE_NUMBER - valueFrom: - configMapKeyRef: - key: server.http.cookie.maxnumber - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_LISTEN_ADDRESS - valueFrom: - configMapKeyRef: - key: server.listen.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_METRICS_LISTEN_ADDRESS - valueFrom: - configMapKeyRef: - key: server.metrics.listen.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_OTLP_ADDRESS - valueFrom: - configMapKeyRef: - key: otlp.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_OTLP_INSECURE - valueFrom: - configMapKeyRef: - key: otlp.insecure - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_OTLP_HEADERS - valueFrom: - configMapKeyRef: - key: otlp.headers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_OTLP_ATTRS - valueFrom: - configMapKeyRef: - key: otlp.attrs - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_NAMESPACES - valueFrom: - configMapKeyRef: - key: application.namespaces - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION - valueFrom: - configMapKeyRef: - key: server.enable.proxy.extension - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8SCLIENT_RETRY_MAX - valueFrom: - configMapKeyRef: - key: server.k8sclient.retry.max - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8SCLIENT_RETRY_BASE_BACKOFF - valueFrom: - configMapKeyRef: - key: server.k8sclient.retry.base.backoff - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_API_CONTENT_TYPES - valueFrom: - configMapKeyRef: - key: server.api.content.types - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT - valueFrom: - configMapKeyRef: - key: server.webhook.parallelism.limit - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_NEW_GIT_FILE_GLOBBING - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.new.git.file.globbing - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.scm.root.ca.path - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ALLOWED_SCM_PROVIDERS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.allowed.scm.providers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_SCM_PROVIDERS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.scm.providers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_GITHUB_API_METRICS - valueFrom: - configMapKeyRef: - key: applicationsetcontroller.enable.github.api.metrics - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_HYDRATOR_ENABLED - valueFrom: - configMapKeyRef: - key: hydrator.enabled - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SYNC_WITH_REPLACE_ALLOWED - valueFrom: - configMapKeyRef: - key: server.sync.replace.allowed - name: argocd-cmd-params-cm - optional: true - image: quay.io/argoproj/argocd:v3.4.5 - imagePullPolicy: Always - livenessProbe: - httpGet: - path: /healthz?full=true - port: 8080 - initialDelaySeconds: 3 - periodSeconds: 30 - timeoutSeconds: 5 - name: argocd-server - ports: - - containerPort: 8080 - - containerPort: 8083 - readinessProbe: - httpGet: - path: /healthz - port: 8080 - initialDelaySeconds: 3 - periodSeconds: 30 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /app/config/ssh - name: ssh-known-hosts - - mountPath: /app/config/tls - name: tls-certs - - mountPath: /app/config/server/tls - name: argocd-repo-server-tls - - mountPath: /home/argocd - name: plugins-home - - mountPath: /tmp - name: tmp - - mountPath: /home/argocd/params - name: argocd-cmd-params-cm - nodeSelector: - kubernetes.io/os: linux - serviceAccountName: argocd-server - volumes: - - emptyDir: {} - name: plugins-home - - emptyDir: {} - name: tmp - - configMap: - name: argocd-ssh-known-hosts-cm - name: ssh-known-hosts - - configMap: - name: argocd-tls-certs-cm - name: tls-certs - - name: argocd-repo-server-tls - secret: - items: - - key: tls.crt - path: tls.crt - - key: tls.key - path: tls.key - - key: ca.crt - path: ca.crt - optional: true - secretName: argocd-repo-server-tls - - configMap: - items: - - key: server.profile.enabled - path: profiler.enabled - name: argocd-cmd-params-cm - optional: true - name: argocd-cmd-params-cm ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: argocd-application-controller - serviceName: argocd-application-controller - template: - metadata: - labels: - app.kubernetes.io/name: argocd-application-controller - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/name: argocd-application-controller - topologyKey: kubernetes.io/hostname - weight: 100 - - podAffinityTerm: - labelSelector: - matchLabels: - app.kubernetes.io/part-of: argocd - topologyKey: kubernetes.io/hostname - weight: 5 - containers: - - args: - - /usr/local/bin/argocd-application-controller - env: - - name: REDIS_PASSWORD - valueFrom: - secretKeyRef: - key: auth - name: argocd-redis - - name: GRPC_ENABLE_TXT_SERVICE_CONFIG - valueFrom: - configMapKeyRef: - key: controller.grpc.enable.txt.service.config - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_CONTROLLER_REPLICAS - value: "1" - - name: ARGOCD_RECONCILIATION_TIMEOUT - valueFrom: - configMapKeyRef: - key: timeout.reconciliation - name: argocd-cm - optional: true - - name: ARGOCD_HARD_RECONCILIATION_TIMEOUT - valueFrom: - configMapKeyRef: - key: timeout.hard.reconciliation - name: argocd-cm - optional: true - - name: ARGOCD_RECONCILIATION_JITTER - valueFrom: - configMapKeyRef: - key: timeout.reconciliation.jitter - name: argocd-cm - optional: true - - name: ARGOCD_REPO_ERROR_GRACE_PERIOD_SECONDS - valueFrom: - configMapKeyRef: - key: controller.repo.error.grace.period.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER - valueFrom: - configMapKeyRef: - key: repo.server - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_TIMEOUT_SECONDS - valueFrom: - configMapKeyRef: - key: controller.repo.server.timeout.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_STATUS_PROCESSORS - valueFrom: - configMapKeyRef: - key: controller.status.processors - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_OPERATION_PROCESSORS - valueFrom: - configMapKeyRef: - key: controller.operation.processors - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_LOGFORMAT - valueFrom: - configMapKeyRef: - key: controller.log.format - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_LOGLEVEL - valueFrom: - configMapKeyRef: - key: controller.log.level - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_LOG_FORMAT_TIMESTAMP - valueFrom: - configMapKeyRef: - key: log.format.timestamp - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_QPS - valueFrom: - configMapKeyRef: - key: controller.k8s.client.qps - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_BURST - valueFrom: - configMapKeyRef: - key: controller.k8s.client.burst - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS - valueFrom: - configMapKeyRef: - key: controller.k8s.client.max.idle.connections - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_TIMEOUT - valueFrom: - configMapKeyRef: - key: controller.k8s.tcp.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_KEEPALIVE - valueFrom: - configMapKeyRef: - key: controller.k8s.tcp.keepalive - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TLS_HANDSHAKE_TIMEOUT - valueFrom: - configMapKeyRef: - key: controller.k8s.tls.handshake.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8S_TCP_IDLE_TIMEOUT - valueFrom: - configMapKeyRef: - key: controller.k8s.tcp.idle.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_METRICS_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: controller.metrics.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_TIMEOUT_SECONDS - valueFrom: - configMapKeyRef: - key: controller.self.heal.timeout.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS - valueFrom: - configMapKeyRef: - key: controller.self.heal.backoff.timeout.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR - valueFrom: - configMapKeyRef: - key: controller.self.heal.backoff.factor - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS - valueFrom: - configMapKeyRef: - key: controller.self.heal.backoff.cap.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_COOLDOWN_SECONDS - valueFrom: - configMapKeyRef: - key: controller.self.heal.backoff.cooldown.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_SYNC_WAVE_DELAY - valueFrom: - configMapKeyRef: - key: controller.sync.wave.delay.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SYNC_TIMEOUT - valueFrom: - configMapKeyRef: - key: controller.sync.timeout.seconds - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT - valueFrom: - configMapKeyRef: - key: controller.repo.server.plaintext - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS - valueFrom: - configMapKeyRef: - key: controller.repo.server.strict.tls - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_PERSIST_RESOURCE_HEALTH - valueFrom: - configMapKeyRef: - key: controller.resource.health.persist - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APP_STATE_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: controller.app.state.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: REDIS_SERVER - valueFrom: - configMapKeyRef: - key: redis.server - name: argocd-cmd-params-cm - optional: true - - name: REDIS_COMPRESSION - valueFrom: - configMapKeyRef: - key: redis.compression - name: argocd-cmd-params-cm - optional: true - - name: REDISDB - valueFrom: - configMapKeyRef: - key: redis.db - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_DEFAULT_CACHE_EXPIRATION - valueFrom: - configMapKeyRef: - key: controller.default.cache.expiration - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ADDRESS - valueFrom: - configMapKeyRef: - key: otlp.address - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_OTLP_INSECURE - valueFrom: - configMapKeyRef: - key: otlp.insecure - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_OTLP_HEADERS - valueFrom: - configMapKeyRef: - key: otlp.headers - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_OTLP_ATTRS - valueFrom: - configMapKeyRef: - key: otlp.attrs - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_NAMESPACES - valueFrom: - configMapKeyRef: - key: application.namespaces - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_CONTROLLER_SHARDING_ALGORITHM - valueFrom: - configMapKeyRef: - key: controller.sharding.algorithm - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_KUBECTL_PARALLELISM_LIMIT - valueFrom: - configMapKeyRef: - key: controller.kubectl.parallelism.limit - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8SCLIENT_RETRY_MAX - valueFrom: - configMapKeyRef: - key: controller.k8sclient.retry.max - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_K8SCLIENT_RETRY_BASE_BACKOFF - valueFrom: - configMapKeyRef: - key: controller.k8sclient.retry.base.backoff - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_SERVER_SIDE_DIFF - valueFrom: - configMapKeyRef: - key: controller.diff.server.side - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT - valueFrom: - configMapKeyRef: - key: controller.ignore.normalizer.jq.timeout - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_HYDRATOR_ENABLED - valueFrom: - configMapKeyRef: - key: hydrator.enabled - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_CLUSTER_CACHE_BATCH_EVENTS_PROCESSING - valueFrom: - configMapKeyRef: - key: controller.cluster.cache.batch.events.processing - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_CLUSTER_CACHE_EVENTS_PROCESSING_INTERVAL - valueFrom: - configMapKeyRef: - key: controller.cluster.cache.events.processing.interval - name: argocd-cmd-params-cm - optional: true - - name: ARGOCD_APPLICATION_CONTROLLER_COMMIT_SERVER - valueFrom: - configMapKeyRef: - key: commit.server - name: argocd-cmd-params-cm - optional: true - - name: KUBECACHEDIR - value: /tmp/kubecache - image: quay.io/argoproj/argocd:v3.4.5 - imagePullPolicy: Always - name: argocd-application-controller - ports: - - containerPort: 8082 - readinessProbe: - httpGet: - path: /healthz - port: 8082 - initialDelaySeconds: 5 - periodSeconds: 10 - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /app/config/controller/tls - name: argocd-repo-server-tls - - mountPath: /home/argocd - name: argocd-home - - mountPath: /home/argocd/params - name: argocd-cmd-params-cm - - mountPath: /tmp - name: argocd-application-controller-tmp - workingDir: /home/argocd - nodeSelector: - kubernetes.io/os: linux - serviceAccountName: argocd-application-controller - volumes: - - emptyDir: {} - name: argocd-home - - emptyDir: {} - name: argocd-application-controller-tmp - - name: argocd-repo-server-tls - secret: - items: - - key: tls.crt - path: tls.crt - - key: tls.key - path: tls.key - - key: ca.crt - path: ca.crt - optional: true - secretName: argocd-repo-server-tls - - configMap: - items: - - key: controller.profile.enabled - path: profiler.enabled - name: argocd-cmd-params-cm - optional: true - name: argocd-cmd-params-cm ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/component: application-controller - app.kubernetes.io/name: argocd-application-controller - app.kubernetes.io/part-of: argocd - name: argocd-application-controller-network-policy -spec: - ingress: - - from: - - namespaceSelector: {} - ports: - - port: 8082 - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-application-controller - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/component: applicationset-controller - app.kubernetes.io/name: argocd-applicationset-controller - app.kubernetes.io/part-of: argocd - name: argocd-applicationset-controller-network-policy -spec: - ingress: - - from: - - namespaceSelector: {} - ports: - - port: 7000 - protocol: TCP - - port: 8080 - protocol: TCP - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-applicationset-controller - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/component: notifications-controller - app.kubernetes.io/name: argocd-notifications-controller - app.kubernetes.io/part-of: argocd - name: argocd-notifications-controller-network-policy -spec: - ingress: - - from: - - namespaceSelector: {} - ports: - - port: 9001 - protocol: TCP - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-notifications-controller - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/component: redis - app.kubernetes.io/name: argocd-redis - app.kubernetes.io/part-of: argocd - name: argocd-redis-network-policy -spec: - ingress: - - from: - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-server - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-repo-server - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-application-controller - ports: - - port: 6379 - protocol: TCP - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-redis - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/component: repo-server - app.kubernetes.io/name: argocd-repo-server - app.kubernetes.io/part-of: argocd - name: argocd-repo-server-network-policy -spec: - ingress: - - from: - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-server - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-application-controller - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-notifications-controller - - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-applicationset-controller - ports: - - port: 8081 - protocol: TCP - - from: - - namespaceSelector: {} - ports: - - port: 8084 - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-repo-server - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/component: server - app.kubernetes.io/name: argocd-server - app.kubernetes.io/part-of: argocd - name: argocd-server-network-policy -spec: - ingress: - - {} - podSelector: - matchLabels: - app.kubernetes.io/name: argocd-server - policyTypes: - - Ingress diff --git a/apps/argocd/argocd-oidc-secret-sealed.yaml b/apps/argocd/argocd-oidc-secret-sealed.yaml index e46b875..ff54955 100644 --- a/apps/argocd/argocd-oidc-secret-sealed.yaml +++ b/apps/argocd/argocd-oidc-secret-sealed.yaml @@ -16,4 +16,6 @@ spec: creationTimestamp: null name: argocd-oidc-secret namespace: argocd + labels: + app.kubernetes.io/part-of: argocd type: Opaque diff --git a/apps/argocd/kustomization.yaml b/apps/argocd/kustomization.yaml index 8d75623..fb0f4a8 100644 --- a/apps/argocd/kustomization.yaml +++ b/apps/argocd/kustomization.yaml @@ -4,82 +4,7 @@ kind: Kustomization namespace: argocd resources: - - argocd-install.yaml - argocd-ingress.yaml - argocd-oidc-secret-sealed.yaml - repo-basicstack-org-secret-sealed.yaml - repo-stack-basicstack-de-secret-sealed.yaml - -patches: - # Add memory limits to prevent OOM incidents (DEV-281) - - target: - kind: StatefulSet - name: argocd-application-controller - patch: |- - - op: add - path: /spec/template/spec/containers/0/resources - value: - limits: - memory: 512Mi - requests: - memory: 256Mi - - - target: - kind: Deployment - name: argocd-repo-server - patch: |- - - op: add - path: /spec/template/spec/containers/0/resources - value: - limits: - memory: 512Mi - requests: - memory: 256Mi - - - target: - kind: Deployment - name: argocd-redis - patch: |- - - op: add - path: /spec/template/spec/containers/0/resources - value: - limits: - memory: 256Mi - requests: - memory: 128Mi - - - target: - kind: Deployment - name: argocd-server - patch: |- - - op: add - path: /spec/template/spec/containers/0/resources - value: - limits: - memory: 256Mi - requests: - memory: 128Mi - - - target: - kind: Deployment - name: argocd-notifications-controller - patch: |- - - op: add - path: /spec/template/spec/containers/0/resources - value: - limits: - memory: 128Mi - requests: - memory: 64Mi - - - target: - kind: Deployment - name: argocd-applicationset-controller - patch: |- - - op: add - path: /spec/template/spec/containers/0/resources - value: - limits: - memory: 256Mi - requests: - memory: 128Mi diff --git a/apps/argocd/values.yaml b/apps/argocd/values.yaml new file mode 100644 index 0000000..90757ed --- /dev/null +++ b/apps/argocd/values.yaml @@ -0,0 +1,87 @@ +global: + image: + tag: v3.5.1 + +configs: + cm: + url: https://argo.basicstack.de + application.instanceLabelKey: argocd.argoproj.io/instance + resource.exclusions: | + - apiGroups: [cilium.io] + kinds: [CiliumIdentity, CiliumEndpoint, CiliumEndpointSlice] + - apiGroups: [kyverno.io, reports.kyverno.io, wgpolicyk8s.io] + kinds: [PolicyReport, ClusterPolicyReport, EphemeralReport, + ClusterEphemeralReport, AdmissionReport, ClusterAdmissionReport, + BackgroundScanReport, ClusterBackgroundScanReport, UpdateRequest] + oidc.config: | + name: Pocket ID + issuer: https://auth.basicstack.de + clientID: $argocd-oidc-secret:oidc.pocketid.clientId + clientSecret: $argocd-oidc-secret:oidc.pocketid.clientSecret + requestedScopes: [openid, profile, email, groups] + requestedIDTokenClaims: + groups: {essential: true} + + rbac: + policy.default: role:readonly + policy.csv: | + g, argo_admins, role:admin + p, role:admin, applications, *, */*, allow + p, role:admin, clusters, *, *, allow + p, role:admin, repositories, *, *, allow + p, role:admin, projects, *, *, allow + p, role:admin, accounts, *, *, allow + p, role:admin, gpgkeys, *, *, allow + p, role:admin, certificates, *, *, allow + p, role:admin, exec, *, *, allow + + params: + server.insecure: "true" + + ssh: + extraHosts: | + forgejo.forgejo.svc.cluster.local ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnFlDTGVmri7geuOfZ1UvHzCRi1U5BqCq+9MwkrbGkZCmBmUt3ek95JBZzurLvR/0rE624B9tB1AvSJIEIQ1W1YXD4ydiDaHX6J0sip6lEfqhREx0y91C15zHgi9jN0UKidse1g0xlLHpwePUmz8/2BJcLHJuwbZSdUu7+uhDIYtIJ5+3trX3IABNeluRA5TkspPAC0ViEaz4saWgWQWjKL8dsb3jIR94DiWAVpQnhaCBEILkIStJrmWl0O5B10Jr3KWy18szr9UVd8edCkoEXriCR8gx69jHdmuem5WlZPGvsK5Adf0mXE8S3rdyHqWOkDRs6Wlwd9p/1nDY8c8wtE3vqhefCpt1BwpTys9PMmgUfG0at/W+NdvalRqgQz26Bso8Tf7hcfyA/B69U2pvjA0tbgdIGJ5kLeCzpz9kpCYn8wSIuNIJ86BpyfnjRYFCYJVc6Ls86i8j3fEZAjEX7bmbeDBHQnyyH+rjq+Llo8aUd2Uf7HoSA93EjeuWq/Ta+YbWEWrp9Mrd48jnypxHXsiwDzqkm+YGQbXHfasiarxji/eQ5UMMG8hCxpLp1lJLhGN2th4eCLkpwchFr5jZGWgyZCth1WzQGj7NHvDTxKRU6n4MfsEX1B6GF0D60qtM5vZynpmO902mkn6wtxo+pCkDMroj668a62zw3rSS5Bw== + +controller: + resources: + requests: + memory: 512Mi + limits: + memory: 1Gi + +repoServer: + resources: + requests: + memory: 256Mi + limits: + memory: 512Mi + +server: + resources: + requests: + memory: 128Mi + limits: + memory: 256Mi + ingress: + enabled: false + +redis: + resources: + requests: + memory: 128Mi + limits: + memory: 256Mi + +notifications: + resources: + requests: + memory: 64Mi + limits: + memory: 128Mi + +applicationSet: + resources: + requests: + memory: 128Mi + limits: + memory: 256Mi diff --git a/apps/backup/forgejo-backup-cronjob.yaml b/apps/backup/forgejo-backup-cronjob.yaml deleted file mode 100644 index d9598f9..0000000 --- a/apps/backup/forgejo-backup-cronjob.yaml +++ /dev/null @@ -1,80 +0,0 @@ -apiVersion: batch/v1 -kind: CronJob -metadata: - name: forgejo-backup - namespace: backup - labels: - app: forgejo-backup -spec: - schedule: "0 3 * * *" - concurrencyPolicy: Forbid - successfulJobsHistoryLimit: 7 - failedJobsHistoryLimit: 3 - jobTemplate: - spec: - template: - spec: - restartPolicy: OnFailure - containers: - - name: forgejo-backup - image: postgres:16-alpine - imagePullPolicy: IfNotPresent - command: - - /bin/sh - - -lc - - | - set -euo pipefail - ts=$(date -u +%Y%m%dT%H%M%SZ) - BACKUP_DIR="/data/forgejo-${ts}" - mkdir -p "${BACKUP_DIR}" - - echo "[${ts}] Starting Forgejo backup..." - - # Backup PostgreSQL database - echo "[$(date -u +%H:%M:%S)] Dumping PostgreSQL database..." - PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \ - -h forgejo-postgres.forgejo.svc.cluster.local \ - -U "${POSTGRES_USER}" \ - -d "${POSTGRES_DB}" \ - -F c \ - -f "${BACKUP_DIR}/forgejo-db.dump" - - echo "[$(date -u +%H:%M:%S)] Database backup complete: $(ls -lh ${BACKUP_DIR}/forgejo-db.dump | awk '{print $5}')" - - # Create backup manifest - echo "backup_timestamp=${ts}" > "${BACKUP_DIR}/manifest.txt" - echo "type=postgresql_custom_dump" >> "${BACKUP_DIR}/manifest.txt" - echo "database=forgejo" >> "${BACKUP_DIR}/manifest.txt" - echo "restore_cmd=PGPASSWORD= pg_restore -h -U forgejo -d forgejo -F c forgejo-db.dump" >> "${BACKUP_DIR}/manifest.txt" - - # Cleanup old backups (keep 14 days) - find /data -maxdepth 1 -type d -name "forgejo-*" -mtime +14 -exec rm -rf {} + 2>/dev/null || true - - echo "[$(date -u +%H:%M:%S)] Backup complete. Files:" - ls -lh "${BACKUP_DIR}/" - - echo "[$(date -u +%H:%M:%S)] All backups in storage:" - ls -1 /data | grep "forgejo-" - env: - - name: POSTGRES_USER - valueFrom: - secretKeyRef: - name: forgejo-backup-secret - key: POSTGRES_USER - - name: POSTGRES_PASSWORD - valueFrom: - secretKeyRef: - name: forgejo-backup-secret - key: POSTGRES_PASSWORD - - name: POSTGRES_DB - valueFrom: - secretKeyRef: - name: forgejo-backup-secret - key: POSTGRES_DB - volumeMounts: - - name: backup-data - mountPath: /data - volumes: - - name: backup-data - persistentVolumeClaim: - claimName: platform-backup-data diff --git a/apps/backup/forgejo-backup-secret-sealed.yaml b/apps/backup/forgejo-backup-secret-sealed.yaml deleted file mode 100644 index 6db7bc4..0000000 --- a/apps/backup/forgejo-backup-secret-sealed.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: forgejo-backup-secret - namespace: backup -spec: - encryptedData: - POSTGRES_DB: AgBAaN4x6Izo1/jx7eeNHiDefYNqm+Mh482poUIxw+TgBlLloMDJ7biKazDiNUR+Ym7DniMjroVRjMc3Fi4hiqhcDBt06tuiKXe3qX2lEDwkZbe4mZ1VeqMgSqKbM0wY98y9XWN+G3JDHs5GYQsfJDXwQ9+QzOtNGByQw2Pi2jlov3xAo0vqZLucXPmzhhCDzR49CnaouR200U95vhXZesHF4un3qSFzmi5Ty2JGMd8gKo57OOkWs2GysAoMwDuAv5337aBkNicZK4M0ZtlyMS1YHKnHyxCeghAZPvrWTwLgy1mjP9pcZ+cFQB5qN8wIvXDh9URa6xhUB2QQB6+/1v7kFNisLHs1lTGjYnHPvdyXvFF+IIPfS0eqdz2X2aVRJCkBbiJgwTj1fBiBxdEZ/UP7qJUlemGWMn/lWiThubx3pMuqGHEKnEaCo6sC4GKneFkj6isIrmW6oVc9Xwj9QwL0ELglntteFp6/RSKkCgfHwt2BJQVMMRO7KSD3p1aFUy9bhOcecOrlVGWrEyKPZTTfCySLNi+Eg+3Pz3A2heiYid4X4Y4fQOOWYNz2nO5w2/aVvUsLXA7LbPp/7hg9BPDKmsgr48i2nJXzK9Gjt1cQuSiLy3kwQpry3nwPYclTdwVJTjprdRf0vAODq1ohaXfKoYOu/bvfKzWImqjWo7kB33qkQhT6xVocwadDrgmQlecxcM5eIInh - POSTGRES_PASSWORD: AgB9KzB//Le/OMAum6LXDjEw8bH4jaa149ROYUhe6aG2zxVsFJDK55xAxGCCCOt1dpxV/EtzfzQjbf/CCs37D6jnwqEjlE/yhIKTwRN43ns5MJ6UXoGZVRWbw/yWSR1Cb6Z7P1hPg/wBp9mImX2lU8fDqNLYEN5Mze56UBH12peFBGBWRdtFPUqXmVfxZ2nmgBP9sb8KcxP6iMMFcGf5eoBz0Yta2iha4DyMu2cD4lGUfc6ZN5VREAwyOcqgUfTlHmpz6BWBXIQR0hxE1wHDrjyWouxZT9uvjuKalTtE9cEwkZV8qKz8S9Ba7Ms7uMqOnJ4Cm3ODDvmEAlcgP3mYlaoCjhBA4LmVM72cv6wA6xg4qCunbJDM+5rB8RetXPDfhV4LcYZe6QDGj1As7ztoOdNhdvZQwICoH4660xnxLUYPRN1UGKyoMNHrxZuzMWxDgULRyrl7TDy+bKJHv74L9VOSjJnKw8ov7cuomrqnUdrq6eawQquxnJgY0jv4lDs1Xn/5Gq+72TBSHOS5k6E6p7aIfG0QeilOVQK+OetGOyCCEaTH7Zo7URpjHnY+571RSdV86hX+qXPZBwjtHnwjgi5HiGn41k7/R3OrtdTj8lnftn246YGaYlIZ1gPY7jUQKYzMxhthXSow6uBA8IBSBmBL3tS8D9qLfr9yXoJ5CwIlDx2P5XyzwQU0NaPWb0Um+XnxteJRSH4bOSM+O84T9n3oXNqAPWpUlJMkIW5+zaCxHxOiWw3ZJWX1UZRrzw== - POSTGRES_USER: AgBtQxQlKNvZbArIy4GTQd/J88Z6ShZoamMMw7sDSXsm0UwnToY8WKJflG+ydG3zzNCIfT1S2bcb0XVZbiDEgWySwct/CS6KtafLJqElJ9e65i4EOsdklBiUssjsceWC8yRoUIqrblwP6vqwrwhkdI46QaIcHJkdNmq/giT3yQqx35Mv/slJvoLwom76bZgs51jm22VjEAzcPA0d9rbe9flKXVGf6OmQpViMiV8OSDUgQ2Yjx6rQ5zFvCUdvoX6UIFxt9xmA75YiFgltxElOAWLRMa/CvdA6ZIY9jy1oD/gDIPjCL9pm4u5wM24mkQWMCfZiHAsDfkrxkMOsDlI8fRvFO4tMZgDnFNhi1EFOR1ga1WiK4NRKprKQ5WN0NjSuh59DxGSuKrg0WQjg88oXUdfYg7d9oqZiL7WUWVMWwtyLyW1wKuVR8qIoPxYcY88BaEOtw7tQ8odNszWBigsXCeHP9zVpVQjdPhDyGiN38MqftqQS1ywj/oLHWBC2UbH09I1VuAokiUl92CBAt7EiuvyVyYBv0yZlFT6ReatPeUfCWfAWmXkTg6iOiW+Y4+9HPuhBVjhyUw20WifEajNAbva8azSyc4S1LIIOpIlvoQp9joG7a65yRFjt1KzFoh4KDg+WJC1MNtBgqvMBS/4jOiBcrYh2lKIgGO/kBznPqVlZCDn8tdJyxGth56Td/QGfLdCPOxea1g8q - template: - metadata: - creationTimestamp: null - name: forgejo-backup-secret - namespace: backup - type: Opaque diff --git a/apps/basicstack-org/README.md b/apps/basicstack-org/README.md new file mode 100644 index 0000000..6e209da --- /dev/null +++ b/apps/basicstack-org/README.md @@ -0,0 +1,34 @@ +# BasicStack.org Web Application + +This directory contains the Kubernetes deployment manifests for the BasicStack.org frontend website. + +## Architecture + +- **Application**: Nuxt 3 + Vue 3 frontend +- **Container Registry**: Harbor at `10.106.73.119:5000` +- **Image Name**: `basicstack-web` +- **Namespace**: `basicstack-web` + +## Components + +- `configmap.yaml` - Environment configuration +- `deployment.yaml` - Application deployment and service +- `ingress.yaml` - Traefik ingress rules for basicstack.org and www.basicstack.org + +## CI/CD + +The application is built and pushed to Harbor by the Forgejo Actions workflow in the [basicstack.org repository](https://forgejo.basicstack.de/basicstack/basicstack.org). + +The workflow: +1. Builds the Docker image from the Dockerfile +2. Pushes to Harbor registry at `10.106.73.119:5000/basicstack-web:latest` +3. Argo CD automatically syncs the deployment when new images are available + +## Image Pull Policy + +The deployment uses `imagePullPolicy: Always` to ensure the latest image is pulled on each pod restart, enabling continuous deployment of new builds. + +## Domains + +- Primary: https://basicstack.org +- Redirect: https://www.basicstack.org → https://basicstack.org diff --git a/apps/basicstack-org/configmap.yaml b/apps/basicstack-org/configmap.yaml new file mode 100644 index 0000000..3266d07 --- /dev/null +++ b/apps/basicstack-org/configmap.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: basicstack-web-config + namespace: basicstack-web +data: + DIRECTUS_URL: "https://directus.basicstack.de" + SITE_URL: "https://basicstack.org" + NODE_ENV: "production" diff --git a/apps/basicstack-org/deployment.yaml b/apps/basicstack-org/deployment.yaml new file mode 100644 index 0000000..21ee59a --- /dev/null +++ b/apps/basicstack-org/deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: basicstack-web + namespace: basicstack-web + labels: + app: basicstack-web +spec: + replicas: 2 + selector: + matchLabels: + app: basicstack-web + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + template: + metadata: + labels: + app: basicstack-web + spec: + containers: + - name: basicstack-web + image: harbor.basicstack.de/basicstack/basicstack-web:latest + imagePullPolicy: Always + ports: + - containerPort: 3000 + name: http + envFrom: + - configMapRef: + name: basicstack-web-config + resources: + requests: + memory: "128Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "200m" + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 30 + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: basicstack-web + namespace: basicstack-web +spec: + selector: + app: basicstack-web + ports: + - port: 80 + targetPort: 3000 + name: http diff --git a/apps/basicstack-org/ingress.yaml b/apps/basicstack-org/ingress.yaml new file mode 100644 index 0000000..961c9aa --- /dev/null +++ b/apps/basicstack-org/ingress.yaml @@ -0,0 +1,50 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: basicstack-web + namespace: basicstack-web + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + traefik.ingress.kubernetes.io/router.middlewares: basicstack-web-redirect@kubernetescrd +spec: + ingressClassName: traefik + tls: + - hosts: + - basicstack.org + - www.basicstack.org + secretName: basicstack-org-tls + rules: + - host: basicstack.org + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: basicstack-web + port: + number: 80 + - host: www.basicstack.org + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: basicstack-web + port: + number: 80 +--- +# Redirect www to non-www +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: redirect + namespace: basicstack-web +spec: + redirectRegex: + regex: "^https://www\\.basicstack\\.org/(.*)" + replacement: "https://basicstack.org/${1}" + permanent: true diff --git a/apps/bookstack/bookstack-deployment.yaml b/apps/bookstack/bookstack-deployment.yaml index f004ec9..4197b28 100644 --- a/apps/bookstack/bookstack-deployment.yaml +++ b/apps/bookstack/bookstack-deployment.yaml @@ -100,6 +100,8 @@ metadata: namespace: bookstack spec: replicas: 1 + strategy: + type: Recreate selector: matchLabels: app: bookstack @@ -201,7 +203,7 @@ spec: cpu: "1000m" livenessProbe: httpGet: - path: / + path: /status port: 80 initialDelaySeconds: 60 periodSeconds: 10 @@ -209,7 +211,7 @@ spec: failureThreshold: 6 readinessProbe: httpGet: - path: / + path: /status port: 80 initialDelaySeconds: 30 periodSeconds: 5 diff --git a/apps/directus/directus-admin-credentials-sealed.yaml b/apps/directus/directus-admin-credentials-sealed.yaml index 0cbb888..d56be80 100644 --- a/apps/directus/directus-admin-credentials-sealed.yaml +++ b/apps/directus/directus-admin-credentials-sealed.yaml @@ -14,4 +14,6 @@ spec: creationTimestamp: null name: directus-admin-credentials namespace: directus + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous type: Opaque diff --git a/apps/directus/directus-agent-token-sealed.yaml b/apps/directus/directus-agent-token-sealed.yaml index a66aa4a..9a18f39 100644 --- a/apps/directus/directus-agent-token-sealed.yaml +++ b/apps/directus/directus-agent-token-sealed.yaml @@ -13,4 +13,6 @@ spec: creationTimestamp: null name: directus-agent-token namespace: directus + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous type: Opaque diff --git a/apps/directus/directus-app-secrets-sealed.yaml b/apps/directus/directus-app-secrets-sealed.yaml index 30fb70d..026dba6 100644 --- a/apps/directus/directus-app-secrets-sealed.yaml +++ b/apps/directus/directus-app-secrets-sealed.yaml @@ -14,4 +14,6 @@ spec: creationTimestamp: null name: directus-app-secrets namespace: directus + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous type: Opaque diff --git a/apps/directus/directus-backup-cronjob.yaml b/apps/directus/directus-backup-cronjob.yaml new file mode 100644 index 0000000..8dc167e --- /dev/null +++ b/apps/directus/directus-backup-cronjob.yaml @@ -0,0 +1,87 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: directus-backup + namespace: directus + labels: + app: directus + component: backup +spec: + schedule: "0 2 * * *" + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + template: + metadata: + labels: + app: directus + component: backup + spec: + restartPolicy: OnFailure + containers: + - name: backup + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + set -e + BACKUP_DIR="/backups/directus" + BACKUP_FILE="${BACKUP_DIR}/directus-backup-$(date +%Y%m%d-%H%M%S).sql.gz" + echo "Starting Directus database backup to $BACKUP_FILE" + + # Create backup directory if it doesn't exist + mkdir -p "$BACKUP_DIR" + + # Perform PostgreSQL dump and compress + PGPASSWORD="$POSTGRES_PASSWORD" pg_dump \ + -h directus-postgres \ + -U directus \ + -d directus \ + --clean \ + --if-exists \ + --no-owner \ + --no-privileges \ + | gzip > "$BACKUP_FILE" + + # Verify backup was created + if [ -f "$BACKUP_FILE" ]; then + SIZE=$(du -h "$BACKUP_FILE" | cut -f1) + echo "Backup completed successfully: $BACKUP_FILE ($SIZE)" + ls -lh "$BACKUP_FILE" + else + echo "ERROR: Backup file was not created" + exit 1 + fi + + # Clean up backups older than 30 days + echo "Cleaning up backups older than 30 days..." + find "$BACKUP_DIR" -name "directus-backup-*.sql.gz" -type f -mtime +30 -delete + + # Show remaining backups + echo "Current backups:" + ls -lh "$BACKUP_DIR" | grep "directus-backup-" || echo "No backups found" + + echo "Backup job completed" + env: + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: directus-db-credentials + key: postgres-password + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + volumeMounts: + - name: backup-storage + mountPath: /backups + volumes: + - name: backup-storage + hostPath: + path: /var/backups + type: DirectoryOrCreate diff --git a/apps/directus/directus-db-credentials-sealed.yaml b/apps/directus/directus-db-credentials-sealed.yaml index a39303c..79aba80 100644 --- a/apps/directus/directus-db-credentials-sealed.yaml +++ b/apps/directus/directus-db-credentials-sealed.yaml @@ -14,4 +14,6 @@ spec: creationTimestamp: null name: directus-db-credentials namespace: directus + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous type: Opaque diff --git a/apps/directus/directus-db-pvc.yaml b/apps/directus/directus-db-pvc.yaml new file mode 100644 index 0000000..b53634b --- /dev/null +++ b/apps/directus/directus-db-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: directus-db + namespace: directus + labels: + app: directus + component: database +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 20Gi diff --git a/apps/directus/directus-deployment.yaml b/apps/directus/directus-deployment.yaml new file mode 100644 index 0000000..43ad1ee --- /dev/null +++ b/apps/directus/directus-deployment.yaml @@ -0,0 +1,154 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: directus + namespace: directus + labels: + app: directus + component: app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: directus + component: app + template: + metadata: + labels: + app: directus + component: app + spec: + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h directus-postgres -U directus; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + securityContext: + fsGroup: 1000 + containers: + - name: directus + image: directus/directus:12.3.0 + ports: + - name: http + containerPort: 8055 + env: + - name: DB_CLIENT + value: pg + - name: DB_HOST + value: directus-postgres + - name: DB_PORT + value: "5432" + - name: DB_DATABASE + value: directus + - name: DB_USER + value: directus + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: directus-db-credentials + key: postgres-password + - name: KEY + valueFrom: + secretKeyRef: + name: directus-app-secrets + key: key + - name: SECRET + valueFrom: + secretKeyRef: + name: directus-app-secrets + key: secret + - name: ADMIN_EMAIL + valueFrom: + secretKeyRef: + name: directus-admin-credentials + key: admin-email + - name: ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: directus-admin-credentials + key: admin-password + - name: LICENSE_KEY + valueFrom: + secretKeyRef: + name: directus-license + key: license-key + - name: PUBLIC_URL + value: https://directus.basicstack.de + - name: AUTH_PROVIDERS + value: openid + - name: AUTH_OPENID_DRIVER + value: openid + - name: AUTH_OPENID_CLIENT_ID + valueFrom: + secretKeyRef: + name: directus-oidc-secret + key: client-id + - name: AUTH_OPENID_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: directus-oidc-secret + key: client-secret + - name: AUTH_OPENID_ISSUER_URL + valueFrom: + secretKeyRef: + name: directus-oidc-secret + key: issuer-url + - name: AUTH_OPENID_IDENTIFIER_KEY + value: email + - name: AUTH_OPENID_ALLOW_PUBLIC_REGISTRATION + value: "true" + - name: AUTH_OPENID_DEFAULT_ROLE_ID + value: "" + - name: AUTH_OPENID_ICON + value: account_circle + - name: AUTH_OPENID_LABEL + value: SSO Login + - name: AUTH_OPENID_SCOPE + value: openid email profile + - name: CORS_ENABLED + value: "true" + - name: CORS_ORIGIN + value: "true" + - name: CACHE_ENABLED + value: "false" + - name: RATE_LIMITER_ENABLED + value: "true" + - name: RATE_LIMITER_POINTS + value: "50" + - name: RATE_LIMITER_DURATION + value: "1" + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + livenessProbe: + tcpSocket: + port: 8055 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: / + port: 8055 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 3 + volumeMounts: + - name: uploads + mountPath: /directus/uploads + volumes: + - name: uploads + persistentVolumeClaim: + claimName: directus-uploads diff --git a/apps/directus/directus-ingress.yaml b/apps/directus/directus-ingress.yaml new file mode 100644 index 0000000..7c2a87e --- /dev/null +++ b/apps/directus/directus-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: directus + namespace: directus + labels: + app: directus + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + tls: + - hosts: + - directus.basicstack.de + secretName: directus-tls + rules: + - host: directus.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: directus + port: + number: 80 diff --git a/apps/directus/directus-license-sealed.yaml b/apps/directus/directus-license-sealed.yaml new file mode 100644 index 0000000..ba75776 --- /dev/null +++ b/apps/directus/directus-license-sealed.yaml @@ -0,0 +1,17 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: directus-license + namespace: directus +spec: + encryptedData: + license-key: AgBnmjrrBIYuOegTmt2AEUSTPXXUcciYx5ubGVyICDKAgLfnCFZgDzjOfnMCEu+aA6zQ3AwW92wWcftHbDzZjxrS2VfW/AprRLgxlSi/OX7sYncaPwo9v2/iFOzC5E+1OuJiZXIfZp1nboXf16Ujck23k4HRHSFk/y3+XM7WQWGg4h6pl7ZacN/in+fu3mYlB8QSkE0eZcsVS+A7WDHGZQzkJfGwVasEBxVfMPuooCvrv0EoenNEofYCz6LZ4dbsI7EnsbGwX3d5KN9pHzEkacCEnhzAhicDMk7nNMY8JvbTgT8gdlfWyFZoI2kBHfVDAO16+BbblTHJ9SYlYcmDHg3v7MMzyyvZ1XLQ8gQAH8imD54S/thLx8EX7X80c0wbtqJ2cDrOy2diKZjdGXsGNgy/+ErDVV7QqOTY/vH4L2hKE1xI1nHIdHaCjA+4zN9SMroCGioXistgcBnr5TkU42POPzrBCUm5eFYptsV1OIgOpMa7mvmN2uq86Pi8o0hNkLzZ2kW8QUhd3+Edesw9+64iACtPM2hdS6+jDZtyv7LdGnShYDu5tciMFs/dElE0UQW6HqaZ7z/PnJlhlx88mIijD2YsFo+M/8JFojf6iLFyWP6TVPK51yf/lqGwK/4S4c9w4MVfhiTrSmlMk9hOkZy1i91p4xnzaWuBAX8U9XEzN4+qJeHlcQRHiSyi+V45ZD5cZTPqOEfLdOyh/sTXXOQaIicWqHzuCzUv5HzeSQ== + template: + metadata: + creationTimestamp: null + name: directus-license + namespace: directus + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + type: Opaque diff --git a/apps/directus/directus-oidc-secret-sealed.yaml b/apps/directus/directus-oidc-secret-sealed.yaml index 576bd87..61783cc 100644 --- a/apps/directus/directus-oidc-secret-sealed.yaml +++ b/apps/directus/directus-oidc-secret-sealed.yaml @@ -15,4 +15,6 @@ spec: creationTimestamp: null name: directus-oidc-secret namespace: directus + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous type: Opaque diff --git a/apps/directus/directus-postgres-deployment.yaml b/apps/directus/directus-postgres-deployment.yaml new file mode 100644 index 0000000..da07f98 --- /dev/null +++ b/apps/directus/directus-postgres-deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: directus-postgres + namespace: directus + labels: + app: directus + component: database +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: directus + component: database + template: + metadata: + labels: + app: directus + component: database + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - name: postgres + containerPort: 5432 + env: + - name: POSTGRES_DB + value: directus + - name: POSTGRES_USER + value: directus + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: directus-db-credentials + key: postgres-password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + livenessProbe: + exec: + command: + - pg_isready + - -U + - directus + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - directus + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: directus-db diff --git a/apps/directus/directus-postgres-service.yaml b/apps/directus/directus-postgres-service.yaml new file mode 100644 index 0000000..ded1141 --- /dev/null +++ b/apps/directus/directus-postgres-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: directus-postgres + namespace: directus + labels: + app: directus + component: database +spec: + type: ClusterIP + selector: + app: directus + component: database + ports: + - name: postgres + port: 5432 + targetPort: 5432 diff --git a/apps/directus/directus-service.yaml b/apps/directus/directus-service.yaml new file mode 100644 index 0000000..0119152 --- /dev/null +++ b/apps/directus/directus-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: directus + namespace: directus + labels: + app: directus + component: app +spec: + type: ClusterIP + selector: + app: directus + component: app + ports: + - name: http + port: 80 + targetPort: 8055 diff --git a/apps/directus/directus-uploads-pvc.yaml b/apps/directus/directus-uploads-pvc.yaml new file mode 100644 index 0000000..ac6a840 --- /dev/null +++ b/apps/directus/directus-uploads-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: directus-uploads + namespace: directus + labels: + app: directus + component: data +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/dozzle/README.md b/apps/dozzle/README.md new file mode 100644 index 0000000..668b540 --- /dev/null +++ b/apps/dozzle/README.md @@ -0,0 +1,44 @@ +# Dozzle Deployment + +Dozzle is a real-time log viewer for Docker containers running in the Kubernetes cluster. + +## Components + +- **Namespace**: `dozzle` +- **Domain**: `dozzle.basicstack.de` +- **Storage**: 1Gi PVC using `hcloud-volumes-encrypted` storage class +- **Authentication**: Pocket ID OIDC via oauth2-proxy sidecar + +## Architecture + +Dozzle doesn't support native OIDC authentication, so we use oauth2-proxy as a sidecar container: + +1. **oauth2-proxy** (port 4180): Handles OIDC authentication with Pocket ID +2. **Dozzle** (port 8080): Receives authenticated requests from oauth2-proxy with user headers + +The oauth2-proxy authenticates users via Pocket ID OIDC and forwards authenticated requests to Dozzle with `X-Forwarded-User`, `X-Forwarded-Email`, and `X-Forwarded-Preferred-Username` headers. Dozzle is configured with `forward-proxy` authentication to trust these headers. + +## Files + +- `namespace.yaml`: Dozzle namespace +- `service-account.yaml`: Service account with RBAC for accessing pod logs cluster-wide +- `pvc.yaml`: Persistent volume claim for Dozzle settings (1Gi, ReadWriteOnce with Recreate strategy) +- `deployment.yaml`: Dozzle deployment with oauth2-proxy sidecar +- `service.yaml`: Kubernetes service (routes to oauth2-proxy port 4180) +- `ingress.yaml`: Traefik ingress with TLS (routes to oauth2-proxy) +- `dozzle-oidc-sealed.yaml`: Sealed secret with OIDC client credentials and oauth2-proxy cookie secret + +## Pocket ID OIDC Client + +- **Client ID**: `179c13f2-d251-4e1e-b1a0-c070df350c4e` +- **Client Name**: Dozzle +- **Callback URL**: `https://dozzle.basicstack.de/oauth2/callback` +- **Scopes**: `openid profile email` + +## Access + +After deployment, access Dozzle at https://dozzle.basicstack.de and authenticate with Pocket ID credentials. + +## ArgoCD + +The application is managed by ArgoCD via `app-dozzle.yaml` in the parent apps directory. diff --git a/apps/dozzle/deployment.yaml b/apps/dozzle/deployment.yaml new file mode 100644 index 0000000..2b22b10 --- /dev/null +++ b/apps/dozzle/deployment.yaml @@ -0,0 +1,127 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dozzle + namespace: dozzle +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: dozzle + template: + metadata: + labels: + app: dozzle + spec: + serviceAccountName: dozzle + containers: + - name: dozzle + image: amir20/dozzle:v10.7.3 + ports: + - containerPort: 8080 + name: dozzle-http + protocol: TCP + env: + - name: DOZZLE_MODE + value: "k8s" + - name: DOZZLE_LEVEL + value: "info" + - name: DOZZLE_AUTH_PROVIDER + value: "forward-proxy" + - name: DOZZLE_AUTH_HEADER_USER + value: "X-Forwarded-User" + - name: DOZZLE_AUTH_HEADER_EMAIL + value: "X-Forwarded-Email" + - name: DOZZLE_AUTH_HEADER_NAME + value: "X-Forwarded-Preferred-Username" + - name: DOZZLE_NO_ANALYTICS + value: "true" + startupProbe: + httpGet: + path: /healthcheck + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 3 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 20 + readinessProbe: + httpGet: + path: /healthcheck + port: 8080 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /healthcheck + port: 8080 + periodSeconds: 30 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + volumeMounts: + - name: dozzle-data + mountPath: /data + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 250m + memory: 256Mi + - name: oauth2-proxy + image: quay.io/oauth2-proxy/oauth2-proxy:latest + ports: + - containerPort: 4180 + name: http + protocol: TCP + args: + - --http-address=0.0.0.0:4180 + - --upstream=http://127.0.0.1:8080 + - --provider=oidc + - --oidc-issuer-url=https://auth.basicstack.de + - --redirect-url=https://dozzle.basicstack.de/oauth2/callback + - --email-domain=* + - --pass-user-headers=true + - --set-xauthrequest=true + - --pass-access-token=false + - --cookie-secure=true + - --cookie-httponly=true + - --cookie-name=__Host-oauth2-proxy + - --cookie-expire=12h + - --code-challenge-method=S256 + - --proxy-websockets=true + - --pass-host-header=true + - --flush-interval=1s + - --upstream-timeout=3600s + env: + - name: OAUTH2_PROXY_CLIENT_ID + valueFrom: + secretKeyRef: + name: dozzle-oidc + key: client-id + - name: OAUTH2_PROXY_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: dozzle-oidc + key: client-secret + - name: OAUTH2_PROXY_COOKIE_SECRET + valueFrom: + secretKeyRef: + name: dozzle-oidc + key: cookie-secret + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + volumes: + - name: dozzle-data + persistentVolumeClaim: + claimName: dozzle-data diff --git a/apps/dozzle/dozzle-oidc-sealed.yaml b/apps/dozzle/dozzle-oidc-sealed.yaml new file mode 100644 index 0000000..59d0707 --- /dev/null +++ b/apps/dozzle/dozzle-oidc-sealed.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: dozzle-oidc + namespace: dozzle +spec: + encryptedData: + client-id: AgBNals2yE6cxlM9ZRQBj4iWka8F7ldPlmypB9MphG3ywoDkWpPzqlcKmmg3C98s4FwxGuYEr3CcSH46E5DhBsS5Xm5IZ+pJ27VVSdYaJe/3tkSWGTnB56Oq30Gr1pPLToVSp24rSHQVd2VcNdy/lizFGB757b6zKsR+0yVKNETWdXefhbCfZbKmaznGTYd5weGlH9Gz4x79jP2ZaFAgNLL+QYzpmoFHdQTWycyKYj3Hwy4guKxyN1eqNf/70Vk7RbbpiNX/7nF0s3+FaPi/Lqnc3AzrnrCunlGFvwjiKOX4ORAPGJRe8tTybutVAx9I5eAoNyHSuE0nJvobi6PmT9EhFtldRg60Nuw3amIhCty77eyu3tJobw9fJ9c+7Nt7mpS/+pJyKYfYXmyTUzfSX4zRKd/U0+A0VuVTaYjadbyV7s4A2Z0y3trJ+DZlWC2DqgA38FmY59CYt6ZDgFzXooWrapCSy/dW+yzG9XYz2WZQHlYyGSg3mQA3TZETAbg+lunKwZXl+Byc8Dyz2pwIISsFHHrBsr+B7tbx0pBQTvL9cNNAkvJvJ6F8YG+I+6nw5W5WFgB2CN1md/AEITiNB7tqVnC+/BJXXdNLzeUkcXEwLkH9MDQgZBlJrP6kjUlnm5bWZOckv8xy6ZFkbfqtPCdxCv+AhkNa+gCPmRAt1VeR1NsCP+jfzhm8Cu9j3j1CQaPJvmDDiO/r3bWD1ACBwXULa/98BAsEYlENdnl5vpIswKbE1ag= + client-secret: AgBaOMjTwXmqocYK4Xe/i45ByFCVnUiums3hVVxK7xMk8oV+voIsWQRdW8HF6wPfG7T54Ga5AxYj3wousZ/8Z1TIigQTBTD9DIGZfyTndY38cztyNNB0WPgpA2TQ0VHa0OwPC7+V2Pc+IY/eFa9GghkZA5DBjnT7DnyclJcuQwjvdvgOwYP4xtOZ1+nvUq3FSeN6gAyqUH+aVvkhhUAJ44WxqOdL86QlUiSUJaf+Qr9ABQp2Or+76mjt5FW3mxFrHfzDnmbxVq1WOJub8fHlKFw83D5PVnle4ThbVATn2yO7cxuZc/qJc1EB6pPySEIczv62gvbNzZjodXVTk8F+2WanKgBbETJxVo43G+BUJy3IqO4G+2wAhDVR48wugpsT3MQtPvHmDJLiHov5tBzaFN6d8jQZY8ngU9VuZS+grsByVKyEQU7v1lgdIyNTaaWTEn2ppGWmIO+yB6/m5DRa+K6TZHgpQp7jJQqCHSSIevZ0Q0hor8TmdZurJ+20FQX4/QdS2EaNdLPi+wc49hAmCtwBbj0itaGE4DsnP586G9coqD8YLzyPWmiu2HunqFWdenhb+mnw2pFEF+GYQcN+ZGn5KwDJcQAfkE/4pY1SeHpKQVsLiaFah8iydZvK8sW7Ep5+r9tjTUbu9JU8HlwuWwytAEtXXysP9lLMfHLOrHr0EA2J51vfgZT53vMwctE96fsBOR4AVd8Ns7bj/F+snBq7M8+JqPvD3/++5oRWBQtLGA== + cookie-secret: AgArAJOvo7vgt1Gy0mG+5AajPkwJ/vfyV2U193A8NK4NukB2I59ZkqRgWhmEpedbwAxonxtkLlXb8Vu5bdKgeS5No9JhaGYJt0KDtiYhnEx7ov5MCIB4zxAUa+GJixvCAglw0b6awkk7lGT68w09rwQ1DNWdG66nKFNep/RaZFbjWW61MlaIEJ7xjJ8zg797082IyqrcBM6FeHsgOH1Cl/pGAUqtCIX1Oo9NKrp0Qojj0ETc1kNFcixn1hNDLTBDFn3lmceUCz3MADY+0hJEa9AR0kkH5ulGW5hXRCM402W9CZdux/IOweGvWqfg9Jn7f8lhjEx6aUmcUFlumMZRSvAJIvG3Hncjot0Zk5CPm2RCdJ7nr2bibqXKl/LOh9g1qhSRCSM1i7DggcI0qAuCmhwq1zWqatYOVslk5yBYPW2khTfEe4nqf63AjbcORWuZQ4U6fOc1RFD/rb+ISvKDfcIT58cAsT80+NC+2bXQY2wKLrXVV5sv+EksDzUKCP4g1GPLFiTi8paI5R/o6OWe90AX7DD/7pJvXLiw56OhC7GQapTOYk5l6+/Cw+B7U9CzcTAP8E8+b35lWRHYl3iClSUDVlzGvjDju5r6wUVfA1JaaCayN/IXqpf0Xk5p6xeVSU+Tn8hzthh6Ooip6FY2KSBXqNZ+0wCEpBzXlehxzIUXymOgE1ZNpH3MRso+3yMpKG70zGfcziE7llmWb4QhNq7WzZih1CMZuS7mCvsuUnK7+1wiMBYhesnPp9QVrQ== + template: + metadata: + creationTimestamp: null + name: dozzle-oidc + namespace: dozzle + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + type: Opaque diff --git a/apps/dozzle/ingress.yaml b/apps/dozzle/ingress.yaml new file mode 100644 index 0000000..a5d59a1 --- /dev/null +++ b/apps/dozzle/ingress.yaml @@ -0,0 +1,45 @@ +apiVersion: traefik.io/v1alpha1 +kind: ServersTransport +metadata: + name: dozzle + namespace: dozzle +spec: + forwardingTimeouts: + dialTimeout: 30s + idleConnTimeout: 3600s + responseHeaderTimeout: 0s +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: dozzle + namespace: dozzle + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + # Websocket timeout settings - critical for connection stability + traefik.ingress.kubernetes.io/service.serverstransport: dozzle@kubernetescrd + # Websocket support annotations + traefik.ingress.kubernetes.io/service.sticky.cookie: "true" + traefik.ingress.kubernetes.io/service.sticky.cookie.name: "dozzle_session" + traefik.ingress.kubernetes.io/service.sticky.cookie.httpOnly: "true" + traefik.ingress.kubernetes.io/service.sticky.cookie.secure: "true" + traefik.ingress.kubernetes.io/service.passhostheader: "true" +spec: + ingressClassName: traefik + rules: + - host: dozzle.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: dozzle + port: + number: 4180 + tls: + - hosts: + - dozzle.basicstack.de + secretName: dozzle-tls diff --git a/apps/dozzle/namespace.yaml b/apps/dozzle/namespace.yaml new file mode 100644 index 0000000..58ca594 --- /dev/null +++ b/apps/dozzle/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: dozzle diff --git a/apps/dozzle/pvc.yaml b/apps/dozzle/pvc.yaml new file mode 100644 index 0000000..1b895cc --- /dev/null +++ b/apps/dozzle/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: dozzle-data + namespace: dozzle +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 1Gi diff --git a/apps/dozzle/service-account.yaml b/apps/dozzle/service-account.yaml new file mode 100644 index 0000000..e99cfd7 --- /dev/null +++ b/apps/dozzle/service-account.yaml @@ -0,0 +1,55 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: dozzle + namespace: dozzle +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: dozzle +rules: +- apiGroups: + - "" + resources: + - pods + - pods/log + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +- apiGroups: + - metrics.k8s.io + resources: + - pods + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: dozzle +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: dozzle +subjects: +- kind: ServiceAccount + name: dozzle + namespace: dozzle diff --git a/apps/dozzle/service.yaml b/apps/dozzle/service.yaml new file mode 100644 index 0000000..7253efa --- /dev/null +++ b/apps/dozzle/service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: dozzle + namespace: dozzle +spec: + selector: + app: dozzle + ports: + - name: http + port: 4180 + targetPort: 4180 + protocol: TCP diff --git a/apps/forgejo-runner/README.md b/apps/forgejo-runner/README.md new file mode 100644 index 0000000..b5d6abf --- /dev/null +++ b/apps/forgejo-runner/README.md @@ -0,0 +1,103 @@ +# Forgejo Actions Runner + +This directory contains the deployment configuration for the Forgejo Actions runner. + +## Prerequisites + +Before deploying the runner, you need to obtain a registration token from Forgejo. + +### Getting the Registration Token + +**Option 1: Via Forgejo Admin UI** +1. Log in to https://forgejo.basicstack.de as admin +2. Navigate to Site Administration → Actions → Runners +3. Click "Create new Runner" +4. Copy the registration token + +**Option 2: Via API** +```bash +export FORGEJO_TOKEN="your-api-token" +curl -X POST \ + -H "Authorization: token $FORGEJO_TOKEN" \ + https://forgejo.basicstack.de/api/v1/admin/runners/registration-token +``` + +### Creating the Secret + +Once you have the registration token, create a sealed secret: + +```bash +# Create a temporary secret file +kubectl create secret generic forgejo-runner-token \ + --from-literal=token='YOUR_REGISTRATION_TOKEN' \ + --namespace=forgejo \ + --dry-run=client -o yaml > /tmp/runner-token-secret.yaml + +# Seal it with kubeseal +kubeseal --format=yaml < /tmp/runner-token-secret.yaml > apps/forgejo-runner/forgejo-runner-token-sealed.yaml + +# Clean up +rm /tmp/runner-token-secret.yaml +``` + +## Deployment + +The runner is deployed via Argo CD. After creating the sealed secret, apply the Argo CD application: + +```bash +kubectl apply -f apps/app-forgejo-runner.yaml +``` + +## Runner Configuration + +The runner is deployed as a Deployment (single replica) that bind-mounts the +host's `/var/run/docker.sock` to execute job containers. This requires the +Docker Engine (package `docker.io`) to be installed and running on the target +node — see **Node prerequisites** below. + +Configuration: +- **Deployment type**: Deployment (replicas=1) +- **Docker execution**: Host Docker socket (`/var/run/docker.sock`) +- **Concurrent jobs**: 2 (configurable via config.yaml) +- **Labels**: ubuntu-latest:docker://node:24-bookworm, ubuntu-22.04:docker://node:24-bookworm +- **Forgejo URL**: https://forgejo.basicstack.de (external URL for proper webhook/API access) +- **Node selector**: `basicstack.de/docker=true` — schedules only on nodes with the + Docker Engine installed. See **Node prerequisites** below. + +## Node prerequisites (required) + +The runner uses the host's Docker daemon. Every worker node that should be +eligible to run the runner MUST have `docker.io` installed, the `docker` +systemd unit enabled, and be labeled `basicstack.de/docker=true`. Bootstrap a +worker with: + +```bash +ssh root@ +DEBIAN_FRONTEND=noninteractive apt-get update +DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io +systemctl enable --now docker +# from the control plane: +kubectl label node basicstack.de/docker=true --overwrite +``` + +Rolling OS updates that reimage a node will remove Docker. Re-run the bootstrap +after any node reimage. Without Docker, the runner pod fails to mount +`/var/run/docker.sock` (hostPath type check for `Socket` fails); the +nodeSelector prevents that scheduling mistake by pinning to labeled nodes. + +## Troubleshooting + +Check runner logs: +```bash +kubectl logs -n forgejo -l app=forgejo-runner -f +``` + +Check if runner is registered: +```bash +kubectl exec -n forgejo deployment/forgejo-runner -- forgejo-runner list +``` + +## Kubernetes native runners + +Kubernetes native runner still in discussion / under construction:\\ +https://codeberg.org/forgejo/discussions/issues/66 diff --git a/apps/forgejo-runner/forgejo-runner-config.yaml b/apps/forgejo-runner/forgejo-runner-config.yaml new file mode 100644 index 0000000..32e1a8e --- /dev/null +++ b/apps/forgejo-runner/forgejo-runner-config.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: forgejo-runner-config + namespace: forgejo +data: + config.yaml: | + log: + level: debug + runner: + name: k3s-runner-1 + capacity: 2 + labels: + - "ubuntu-latest:docker://node:24-bookworm" + - "ubuntu-22.04:docker://node:24-bookworm" + timeout: 3h + cache: + enabled: true + dir: /data/cache + container: + network: host + privileged: false + options: "-v /usr/bin/docker:/usr/bin/docker:ro" diff --git a/apps/forgejo-runner/forgejo-runner-deployment.yaml b/apps/forgejo-runner/forgejo-runner-deployment.yaml new file mode 100644 index 0000000..6425e95 --- /dev/null +++ b/apps/forgejo-runner/forgejo-runner-deployment.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: forgejo-runner + namespace: forgejo +spec: + replicas: 1 + selector: + matchLabels: + app: forgejo-runner + template: + metadata: + labels: + app: forgejo-runner + spec: + serviceAccountName: forgejo-runner + nodeSelector: + basicstack.de/docker: "true" + containers: + - name: runner + image: code.forgejo.org/forgejo/runner:4.0.1 + imagePullPolicy: IfNotPresent + securityContext: + runAsUser: 0 + runAsGroup: 0 + command: + - sh + - -c + - | + # Wait for Forgejo to be ready (external URL) + while ! wget -q -O- https://forgejo.basicstack.de/api/healthz > /dev/null 2>&1; do + echo "Waiting for Forgejo to be ready..." + sleep 5 + done + + # Re-register if the stored address is the internal cluster URL + if [ -f /data/.runner ] && grep -q "svc.cluster.local" /data/.runner; then + echo "Detected internal cluster URL in .runner — forcing re-registration with external URL" + rm /data/.runner + fi + + # Register runner if not already registered + if [ ! -f /data/.runner ]; then + forgejo-runner register \ + --no-interactive \ + --instance https://forgejo.basicstack.de \ + --token "$RUNNER_TOKEN" \ + --name k3s-runner-1 \ + --labels ubuntu-latest:docker://node:24-bookworm,ubuntu-22.04:docker://node:24-bookworm + fi + + # Start runner + forgejo-runner daemon --config /etc/forgejo-runner/config.yaml + env: + - name: RUNNER_TOKEN + valueFrom: + secretKeyRef: + name: forgejo-runner-token + key: token + - name: DOCKER_HOST + value: unix:///var/run/docker.sock + volumeMounts: + - name: docker-sock + mountPath: /var/run/docker.sock + - name: runner-data + mountPath: /data + - name: config + mountPath: /etc/forgejo-runner + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "2" + memory: 2Gi + volumes: + - name: docker-sock + hostPath: + path: /var/run/docker.sock + type: Socket + - name: runner-data + emptyDir: {} + - name: config + configMap: + name: forgejo-runner-config diff --git a/apps/forgejo-runner/forgejo-runner-serviceaccount.yaml b/apps/forgejo-runner/forgejo-runner-serviceaccount.yaml new file mode 100644 index 0000000..550a8c4 --- /dev/null +++ b/apps/forgejo-runner/forgejo-runner-serviceaccount.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: forgejo-runner + namespace: forgejo +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: forgejo-runner + namespace: forgejo +rules: +- apiGroups: [""] + resources: ["pods", "pods/log"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: forgejo-runner + namespace: forgejo +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: forgejo-runner +subjects: +- kind: ServiceAccount + name: forgejo-runner + namespace: forgejo diff --git a/apps/forgejo-runner/forgejo-runner-token-sealed.yaml b/apps/forgejo-runner/forgejo-runner-token-sealed.yaml new file mode 100644 index 0000000..3380ce5 --- /dev/null +++ b/apps/forgejo-runner/forgejo-runner-token-sealed.yaml @@ -0,0 +1,14 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: forgejo-runner-token + namespace: forgejo +spec: + encryptedData: + token: AgAGd2S0U2AzKqKApPmXMECBG9fNkmdJK6YDRCtQ0mxPCk2OFc5rpIh77FMmf+WcnEtTSlMIGkWQXuOvKIvn78ZNEdYKYuIMTYjzsS+LA9X4gUnhPzvaPj9RmO9NbjFC2Uf3rD7M90SEm1jUY+2F0D2mV2qgYUxfuuG6ksBDFswQkcyDUc5JB+ZZnCYBaNiyY+9/3Y1weQVrf2ckG0fgURiw3AgBw6xcR7i1PEw22z6N+bukf/ykPlUKtEn/elWy7yn6qxXP+QVn4BXPBKIgrVkd1Xq3313U+N2pIGHZXSajjs0oF7mdL5g0dX8lRMS5jLABu3oMPxxRkv4H6D4SKHkloYseQRIQCbOcsgHd6YJjXReGB/9iEvja8Btyg2T6cHQ0m6JgLL6il02tH7NEPEjCAa+ztSBMs/LwqnXkXTzKC+39K6LuQZ/sNBwat7jy6nFn2gvwZ7c0b+GmuxbzukggquXg40toRSBDbAms9J21Owh3g9Hym8oCxgd0Kfa9By3xt+ZXRAMe+RlCnXrF0zWnRLjuL5t+4VVxS4a9IKmB0ggXhfWcF2VtAIxA1r8m/Z2qEqEiBarXspjcSPbOEqzyZdq/iy//KJay1w89D0M/AKweVmhaulJ0iZZrDQm3AR/B11GOgz5bB8IvWDrlZTq0XBQM4g9f8qQPx57OC224tFvNHVORom0tocX9fwZyJbWTLM4ELRX+InRcBh1yHUL46P3GH9TQVmwTI4ZRD5SDpC0kDTKz5J9W + template: + metadata: + creationTimestamp: null + name: forgejo-runner-token + namespace: forgejo diff --git a/apps/forgejo/forgejo-backup-cronjob.yaml b/apps/forgejo/forgejo-backup-cronjob.yaml new file mode 100644 index 0000000..fa99ae4 --- /dev/null +++ b/apps/forgejo/forgejo-backup-cronjob.yaml @@ -0,0 +1,204 @@ +--- +# Forgejo pg_dump → restic → Hetzner Object Storage (DEV-514). +# +# Replaces the legacy volume-based backup that wrote pg_dump files to +# an RWO PVC (`platform-backup-data`). The hcloud volume backing that +# PVC was deleted externally on 2026-08-17; rather than re-provision +# the same legacy pattern, this migrates to the restic→S3 pipeline +# already used for loki/grafana/prometheus (DEV-485…DEV-492). +# +# initContainer runs pg_dump -F c into an emptyDir; main container +# runs `restic backup --stdin-from-command` isn't used here because +# the dump has to complete before restic starts (need a proper exit +# code, and the custom-format dump is not resumable). Instead we +# stage the dump on emptyDir and let restic dedup it into +# `s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/forgejo`. +apiVersion: batch/v1 +kind: CronJob +metadata: + name: forgejo-backup + namespace: forgejo + labels: + app: forgejo-backup + backend: restic +spec: + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + metadata: + labels: + app: forgejo-backup + backend: restic + spec: + backoffLimit: 2 + activeDeadlineSeconds: 3600 + template: + metadata: + labels: + app: forgejo-backup + backend: restic + spec: + restartPolicy: OnFailure + initContainers: + - name: pgdump + image: postgres:16-alpine + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - | + set -eu + echo "=== forgejo pg_dump started at $(date -u +%FT%TZ) ===" + mkdir -p /dump + PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \ + -h forgejo-postgres.forgejo.svc.cluster.local \ + -U "${POSTGRES_USER}" \ + -d "${POSTGRES_DB}" \ + -F c \ + -f /dump/forgejo-db.dump + echo "pg_dump size: $(wc -c < /dump/forgejo-db.dump) bytes" + # Manifest lets a restic-restore consumer identify the + # dump without touching the binary. + { + echo "backup_timestamp=$(date -u +%Y%m%dT%H%M%SZ)" + echo "type=postgresql_custom_dump" + echo "database=${POSTGRES_DB}" + echo "restore_cmd=PGPASSWORD= pg_restore -h -U ${POSTGRES_USER} -d ${POSTGRES_DB} -F c forgejo-db.dump" + } > /dump/manifest.txt + echo "=== forgejo pg_dump finished at $(date -u +%FT%TZ) ===" + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: forgejo-postgres-secret + key: POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: forgejo-postgres-secret + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: forgejo-postgres-secret + key: POSTGRES_DB + volumeMounts: + - name: dump + mountPath: /dump + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 1000m + memory: 512Mi + containers: + - name: restic + image: harbor.basicstack.de/library/restic:0.19.1 + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: forgejo-s3-backup + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: forgejo-s3-backup + key: secret-key + - name: RESTIC_PASSWORD + valueFrom: + secretKeyRef: + name: forgejo-s3-backup + key: restic-password + - name: S3_ENDPOINT + valueFrom: + secretKeyRef: + name: forgejo-s3-backup + key: endpoint + - name: S3_BUCKET + valueFrom: + secretKeyRef: + name: forgejo-s3-backup + key: bucket + - name: RESTIC_REPOSITORY + value: "s3:$(S3_ENDPOINT)/$(S3_BUCKET)/restic/forgejo" + command: + - /bin/sh + - -c + - | + set -eu + echo "=== backup-forgejo-restic started at $(date -u +%FT%TZ) ===" + echo "Repository: ${RESTIC_REPOSITORY}" + + if restic snapshots >/dev/null 2>&1; then + echo "Repo exists, skipping init." + else + echo "Repo missing, initialising..." + restic init + fi + + echo "--- restic backup /source ---" + restic backup /source \ + --tag forgejo \ + --host k3s + + echo "--- restic forget/prune ---" + restic forget --tag forgejo \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 \ + --prune + + echo "--- restic check --read-data-subset=5% ---" + CHECK_STATUS=0 + restic check --read-data-subset=5% || CHECK_STATUS=$? + echo "restic check exit: ${CHECK_STATUS}" + + echo "--- restic stats (repo size) ---" + REPO_SIZE_BYTES=$(restic stats --json --mode raw-data 2>/dev/null \ + | grep -oE '"total_size":[0-9]+' \ + | head -1 \ + | cut -d: -f2) + REPO_SIZE_BYTES=${REPO_SIZE_BYTES:-0} + echo "restic repo size: ${REPO_SIZE_BYTES} bytes" + + # Textfile-collector metrics, same wiring as + # loki/grafana siblings (DEV-494). Atomic write. + { + echo "backup_forgejo_success $([ ${CHECK_STATUS} -eq 0 ] && echo 1 || echo 0)" + echo "backup_forgejo_timestamp_seconds $(date +%s)" + echo "backup_forgejo_check_status ${CHECK_STATUS}" + echo "restic_repo_size_bytes{repo=\"forgejo\"} ${REPO_SIZE_BYTES}" + } > /metrics/backup_forgejo.prom.tmp + mv /metrics/backup_forgejo.prom.tmp /metrics/backup_forgejo.prom + + echo "=== backup-forgejo-restic finished at $(date -u +%FT%TZ) ===" + exit ${CHECK_STATUS} + volumeMounts: + - name: dump + mountPath: /source + readOnly: true + - name: metrics + mountPath: /metrics + - name: cache + mountPath: /root/.cache/restic + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 1500m + memory: 1Gi + volumes: + - name: dump + emptyDir: + sizeLimit: 5Gi + - name: metrics + hostPath: + path: /var/lib/node_exporter/textfile_collector + type: DirectoryOrCreate + - name: cache + emptyDir: {} diff --git a/apps/forgejo/forgejo-deployment.yaml b/apps/forgejo/forgejo-deployment.yaml index fc68376..39cc300 100644 --- a/apps/forgejo/forgejo-deployment.yaml +++ b/apps/forgejo/forgejo-deployment.yaml @@ -5,6 +5,8 @@ metadata: namespace: forgejo spec: replicas: 1 + strategy: + type: Recreate selector: matchLabels: app: forgejo @@ -66,6 +68,10 @@ spec: value: forgejo.basicstack.de - name: FORGEJO__service__DISABLE_REGISTRATION value: "true" + - name: FORGEJO__actions__ENABLED + value: "true" + - name: FORGEJO__actions__DEFAULT_ACTIONS_URL + value: "https://code.forgejo.org" resources: requests: cpu: 250m diff --git a/apps/forgejo/forgejo-s3-backup-sealed.yaml b/apps/forgejo/forgejo-s3-backup-sealed.yaml new file mode 100644 index 0000000..319b3fc --- /dev/null +++ b/apps/forgejo/forgejo-s3-backup-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: forgejo-s3-backup + namespace: forgejo +spec: + encryptedData: + access-key: AgAu/XawJy0uhi3l8vwtd+loShP2ITnbOEZ/9eu6dkypz7l1MmFJhnEGq8Sy1lsg7BKj6epYRI40kDJIAG6KMBD5i7FvLYCvbFuPJaS3yehA8b1ECreHXvx/VYRUZL4K3WtiPO0fXmUNkxAqIcfHOcHtOZ7YcgSSyTw4DaXzngtek4xcTQlisyIVeBgc1dLPY+vHGn10BGkU5R/LnsUQqgGdp6u24Y+m7OrQGqMH8xwtZR7siEEkasu4IWG0QcY+okcMeiCi8mgAF64rdeZnIeEWqRnKHXxwEurtm20NmmCeRR8OgZfWDesgasThsnNSlRySd4EKlP3DMz8Yyluz/LlSkPt24brnP4qKuEoRpCHHOWgOizX8hkkS+Al7Rnpmxv2pt5CGlRw9s/5uPY81PAN9WLIgjs+XIylvaJBSW06nw3v+MjCUr5jfimW9riTLN32cBSurdpNMzlgb5+DJzEvaGNEG+jSjjFj2eSLci23UWySh7oFdmXeBp3kDWCrrNlJOYqJEwAAunTwvtM/1XknW2qJyyJrD7hbWN7SX4Ymb5Q5q18fMGH+IzB/Si1jdBli8pi8B2Gd/jo6RSmhXUheO3xsB2sXYc09GuSBBBBNzr9MsRL3mvUVr22pay+YPcCbwbwtdR0qmrdhEHAychYSJpNGuwGvs0AthRsNGN6hr1/IJqnnSRkGdJPr6k+T3v7XJzouYy+8Dh8r6oPW3hOhHo4HpJw== + bucket: AgAyQVCK+wE0wWxwVjx5DVnI2falTGNzsUrolv1YyoG2Bx6msYe2G00ovxCMJ6/dWws1Q/Sf6yoiFT0IByYflQFhY/ABzXRi7/gFxCWyYU/7yTo7w1Ue0+eEnBKFtkP/ZhIvtfubo6GDDtbmshpp4UtzPq7kB6qvE6oCFyf6awQD2pe/GKX8RZaDx5TCbxPxhedLkYdc7srR/8rFZBnQiXm/2ohtDOwvLA+RVOpnCW/bsVLvLYQmXyhszMv2M3zqXwM9ODYzQ4Y7lIw6T3Hsmel/1hHWJtN8/mlq+Q3Z/NbzhVeEWs8/xbK/ShC9RwZUgBWxAiW04HvwcxVYs60KaJ4oOMLtO4Iko4+iPTNonjra2wmziEpUtsotvtReRjwHdvk1bBn3q6uqzNaJFv9wLc/RVCvKSpqE5KOxmdbke9EDUs4wQHVQCAfFfwnzzpcF/Rf6ML3TUWc+U0W6UjCfVTWwROznpi0E18tA07apx+xm1y+MyuVYnSpZv++vXetnCjA+KFvqZSon/WnKQrmSO2RFR07FcMs6gQ+EOSaRXBPCnEcnF2IJUbJLdFsgpQbzYG6zo3m2KmxN7gaGrTUdhNg/N7LklnNbJML9KRa7e2Rv8t4NGoigOpZG6+soIijZOSfEJF6owN6MP3oecgUgNsnQ5ftKK/RznNaDtIeZ5KByHlQ7CsZ0MIkHBmoEkDt64xWNEoW4NrWPSxas6pOQGZMPMA== + endpoint: AgBhXqp3TdUU6GxsexRIoeHNwjvxnd2v4Any9WGs7gtyZvfdGIuWHIQ+JpsuRDrEq+BvTaX49D0QYoqnoQ6ZgmmGUI7kJxc+Ombcqo/8PAsOmzfdUaz1NcLAeR/UTGu8BDxAQcySSFax2dyK/zkY3jcsmuUCJNJLB57EZwtwx4fyz+ota+zNBE7pY+sDpWkXKHHtWrvNkD7tSLYHQuS3uzATidRixngL+iaH8rQX3ZK6KaK4H4ZdFG3/1Fw0wjLt8+yVBntBnh/bCL9/JLWQWIiHtooQnKXDdEwYQHWhrktti1KDzy/S++o4XVcRG1alzsQ0vV7uzSZAh4xCmnzximJxeJKawzFXRgFUcoEPrOI/nVedYrAU0DzkuwPdqtQDi/fGoqzZPx3PYhB9wv2PJsLg20ZlFcfHYUCad3O6OmO+VlrpYYcUe4oStkOygrmK5bv89fQ4Tov7BB14+RQIo9ogKmLzZu3xStqIVK+/Y8OUC64zyo7FpPMoYKtRW5oawu8pxo7Gtb+27BMMXQBiMEPNf38C4aj3GGJOvm4CFxVHV510/7o2hSW2ITt5X2RzNOEzULJOHD3DvO8wdWzvpBykRFnZ8JTYv77Kbx7bd9Ylx62VXm2a+Bd0K1Nc/sNJE63pglVhdl05oLYsUTrcSsNoJ6Dz3pMKeue3pYrn9KNurG0s+BFfO6I2Aa4/L8iAggXBzf9G1ALSkn7A30C9rMeyV+/1n+q8GpdPqq4ltqLOHVwIZw== + restic-password: AgB1JGFnSDe3N5ZaiT9xShfzUQiyDtGwH2wotYO5Ekk9nqAbS4xTNBXu/1Z1+cB8zVUP80hrPHHbVXjNu47AcLI+Bn8Ozn9fKIZFsnVHXqicG0Y+WHRMpQxuMijVEMl48oUNE+vw+xRhYTzibVWoxur+VEzyPCn8rLUX07tcNhY+jYptNVBYlVVXDrHcOs0s9wpvIlSWmc9RsINMu+6FaedXAs3nKY4gUpWxmNLEpLG8V7D2mNJZHk1adghTN9p8RMGcLtNylDY9W1Sdnziga/NG0WulZf+4IWV6e7wc2oBCSekTofCZuCva8HazJ1EIeqBvt2GuXoG1CjjbPNb2UwZeH21Iol1W0C5So1VFguxHZq5WL44mqD79NLlnSAMmQMSvxf7gXQVXEdtT5UlF9nYCkuQ+K+mT+8iRMy1YSCQSQ9kkD36unkrlYcLXKoPLYxHcoo7fGZ0MPIW8J+P77Hp9Z8jNPCyCaeSJYApCxybO8ujbUGdIr2FCP1z/JDP4/zZat9MkTA6G3H9kDNso7AJBHL/NfkWImvQnveLGj0CuhPpf2zwOylCqTeXrgoN8zztO2/qVDa3VRQbznap0njr0qzVrvOfTxYoiRmkGh3PXZ2mb/NqbeIvCFhj2VyVgavdMchlIbM3fCzXGEdCa2RN+ZYVtcFe/b6wR/qf7lrMaPHsTZiMfYuMJoC9F9wdbkOttT9c36Sob4x+aQYLs7/+XGCJdbw672qot6x6P7GHjY8zVKlMYLSZjKcoD48c= + secret-key: AgAHz7lfqTLG0Kldt/IZGW/o8KKazICHGbdVklXs3Gzs6Mc2KaG3nggkZDRWwY6noochg4Wc9mDDMGY3XIQnEy3PXs/rjQgwym0Dbvc54CvjWUcfmxCJyIqY1uCUxoXp/WAEV18lpWFSkVx+5U9wSccYVmkOJXNJ/MgH0vzJPaNW6xpYDg1XRC4nj72vwqyvZ/gM/LEX41NOpPjBROLGql3uBk5UfzNZG4dBun0aaPiYx5KKAbz8JAbK0umvOOOctTPSv1hIhyv9xJRRB67MW/J8T6mB2YfezsisXSSOAVNuE2JWrafHFxKq/hSNQHSrQRKjt18k6vfxmfkICbgQUZl/7AnoKi/f+76Feu+3wuMT+XPsaJN+iWb3jNzcb1T+Aiv4rGyIA7uCB6FNOc9Ngf5ngDQJw0nDNHDSnUoowgcE96kuPefU1rmn2V+JJcwoYdzYE3Ds48C0rNT+WSIit4551SiyWuj7iCRLhyEf5vhojxkuoyCN+EC1a50XwIkt223EBmy6FFPeBxql6HyJbOE313BPsQU6enPrTKNFd7iZYqJXq1k+SRuGwJhMeo1BBNhmVqxFGOpyxkSVs3VPybeXd82Zk/xF0SWKyBp3XQnLB39ZmT83Z5jwrYfkZmWkkdRvCME6NmlQtftRDDk3efGbgw7bz4Eq0d08KPCezsaDSAGNewonYy3UOiv/CNQg5hQ5G6R55y7+kEMV4nNkjGj0qF1hrZURS0mV4I/7H0OwXpDYiVetfaYI + template: + metadata: + creationTimestamp: null + name: forgejo-s3-backup + namespace: forgejo diff --git a/apps/grist/README.md b/apps/grist/README.md new file mode 100644 index 0000000..961dd01 --- /dev/null +++ b/apps/grist/README.md @@ -0,0 +1,94 @@ +# Grist Deployment + +This directory contains the Kubernetes manifests for the Grist deployment at https://grist.basicstack.de + +Grist is an open-source, self-hostable spreadsheet / database hybrid ([grist-core](https://github.com/gristlabs/grist-core)). + +## Components + +- **grist-deployment.yaml**: Main Grist application deployment + - Image: `gristlabs/grist:1.7.7` + - Port: 8484 + - OIDC login via Pocket ID (`https://auth.basicstack.de`) + - Data persisted at `/persist` + +- **grist-service.yaml**: ClusterIP service exposing port 80 -> 8484 + +- **grist-data-pvc.yaml**: PVC for Grist app data (`/persist`), 20 GiB on `hcloud-volumes-encrypted` + +- **grist-postgres-deployment.yaml** / **grist-postgres-service.yaml**: In-namespace PostgreSQL 16 for the Grist home database (TypeORM connection) + +- **grist-db-pvc.yaml**: PVC for PostgreSQL data, 20 GiB on `hcloud-volumes-encrypted` + +- **grist-ingress.yaml**: Traefik ingress with TLS + - Host: `grist.basicstack.de` + - TLS via cert-manager (`letsencrypt-prod`, DNS-01 via Hetzner webhook) + +- **grist-db-credentials-sealed.yaml**: SealedSecret for PostgreSQL user password +- **grist-app-secrets-sealed.yaml**: SealedSecret with `GRIST_SESSION_SECRET` +- **grist-oidc-secret-sealed.yaml**: SealedSecret with Pocket ID OIDC client credentials + +## OIDC Configuration (Pocket ID) + +- Pocket ID group: `grist` +- Client name in Pocket ID: `Grist` +- Callback URL: `https://grist.basicstack.de/oauth2/callback` +- Client is restricted to members of the `grist` group + +Grist environment variables driving OIDC (from https://pocket-id.org/docs/client-examples/grist): + +``` +GRIST_OIDC_IDP_ISSUER=https://auth.basicstack.de +GRIST_OIDC_IDP_CLIENT_ID= +GRIST_OIDC_IDP_CLIENT_SECRET= +GRIST_OIDC_SP_HOST=https://grist.basicstack.de +GRIST_OIDC_IDP_SCOPES="openid email profile" +GRIST_OIDC_IDP_END_SESSION_ENDPOINT=https://auth.basicstack.de/api/oidc/end-session +GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED=true +GRIST_DEFAULT_EMAIL=andreas.leinen@basicstack.de +GRIST_FORCE_LOGIN=true +``` + +`GRIST_DEFAULT_EMAIL` is used by Grist as the primary admin identity - the value must be a valid Pocket ID user. + +## Storage + +Both PVCs (`grist-data`, `grist-db`) use `hcloud-volumes-encrypted` (Retain reclaim policy) as required for +sensitive workload data on Hetzner. + +## GitOps + +Managed by Argo CD via `apps/app-grist.yaml`: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: grist + namespace: argocd +spec: + project: default + source: + repoURL: git@forgejo.forgejo.svc.cluster.local:basicstack/stack.basicstack.de.git + targetRevision: main + path: apps/grist + destination: + server: https://kubernetes.default.svc + namespace: grist + syncPolicy: + syncOptions: + - CreateNamespace=true +``` + +## Verification + +``` +kubectl -n grist get pods +kubectl -n grist get pvc +kubectl -n grist get ingress +curl -I https://grist.basicstack.de +``` + +## Backup + +The PostgreSQL PVC uses reclaim policy `Retain`. For scheduled logical backups add a `CronJob` like the one in `apps/directus/directus-backup-cronjob.yaml`. diff --git a/apps/grist/grist-app-secrets-sealed.yaml b/apps/grist/grist-app-secrets-sealed.yaml new file mode 100644 index 0000000..112d339 --- /dev/null +++ b/apps/grist/grist-app-secrets-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: grist-app-secrets + namespace: grist +spec: + encryptedData: + session-secret: AgBN2KDg6QQgvhD2WvTG/r4+y6FrY7EXc8kU8N4hUDIh/VJDoyK+xiEHFvuOXMH3ZpNw4qTEpL+a+6LVWM1/c6mdcTIW5J52+hOOoXEtSXJhoBCjljgddlTTxHy9sIdE0694dMgeBJwmnvT9vm1TNQWMbiBaD05Xh4VCX3D908L4l/CiM11M9CLkoMGK7cTz9OCuGtd36k4OUxmw3/mQnafCDyzKf07NRW70Fr/bMGCdKE4Jt1rqWBfIaTb2mq96FCHosFq37keTCh1gu2nKf3iWesfv40rsVNN3wEhATaG2ghmjP2ul21zMOIO5GuCnMcawZv8VR1gyb5f98WOinL1zf5NMPCPRdTl/3G2gpqPc9VRNxOGguup3NLCaV2J+vbH5dgEj/h6zeTXw5bRARRQHAaGhUgFS3PALzubTzaUyNyFOyaMoXbAnE79HU+7QEd3Yjpdujup4Pe6OioK2k7jGWJj/jxZokffCjUsG9+hvNlg2E4ETtXxmHXIkwgTx7nHON6UApnprCtAUatRuMT3H+4VzfgkjV6y2aIHcp21EbH+qDBLo83+sBeUeWYyRtA/W2BGoAhx1CXeBysmEjD/Cls2h2PVhIvUdfNBY9fDgLemuK7MxdKbR8hRb3zHeedLXM4wevg2zCTyyGtizeC3JmpnqO1UHsKBZMGyUh43r+7sh3SyBKVUJxZt0q2JOWibaZNbIsrqcAyb8Te/QZlayZ2s2BxvMvvNrNpcOTYs/KzwwaHva8OUl + template: + metadata: + creationTimestamp: null + name: grist-app-secrets + namespace: grist diff --git a/apps/grist/grist-data-pvc.yaml b/apps/grist/grist-data-pvc.yaml new file mode 100644 index 0000000..e83280e --- /dev/null +++ b/apps/grist/grist-data-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: grist-data + namespace: grist + labels: + app: grist + component: data +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 20Gi diff --git a/apps/grist/grist-db-credentials-sealed.yaml b/apps/grist/grist-db-credentials-sealed.yaml new file mode 100644 index 0000000..b426a69 --- /dev/null +++ b/apps/grist/grist-db-credentials-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: grist-db-credentials + namespace: grist +spec: + encryptedData: + postgres-password: AgA7MFNJ8//EOg4CdfHlSe2yGW6zcJyGsoiWVYlbVkb2GgMZmGQDH/ImYOTfeZXkEQhAWhA0braRB7Prx8fwpON2+6PsOczg+S1N1ykuDMJ+KDuXHhJtm5LNqbgibddX9y6YYInorgxM5mh/ORt2D1IdTwf1AXACkEkp8Ws4IGIfuUCym7kNM77mVD9jW4xyf/c7RE/J3va7L/6SRdx2rdoil8loMzMTHH+lvc738UdFA67hoLkKuseiYgA6NZap5Ppb48GTQul47ji3wH24F/u8DTKiHWbMBxwWO48gQJvAISgXSCW4eiKhkAcm28JACCOd1jT7Asen/yd5qEISB8Fr8q2qwxQwM3kXFIJRf9VVN7BWv6hEMnDO3AhBmcRr+gCPhKial17Varakh0ruBXB4eQDlxGQZ09trwtRPZkOsQnNf38iMQBpDH5U12tASmi1t5ddGKzQQh3EWx1vJyap8xNFf5zK6MuoDEMekHrjcclqgoMWppMH26Daghp2XZl740bV0UhLQBO1TOFEORHBs2DA2A3msmN25selVxjFBHvwLZHQJOHoVHbcqlbF7e2bFSLnij1TBMlPre/llBzoiVYrjHQoHw3fg7xVMrPBT+U1wn4ItDFZTP+nCxnWejtQ/dxzBfMrvBcHVhACsw7VhVkM5nhNFJBda65czyud0BWCSMYJYZEBZjDs220/mhQWIh2/RLANN6pcwJ2ONGyNJs3uo7Q245DlmDTjH5Qppmg== + template: + metadata: + creationTimestamp: null + name: grist-db-credentials + namespace: grist diff --git a/apps/grist/grist-db-pvc.yaml b/apps/grist/grist-db-pvc.yaml new file mode 100644 index 0000000..bf01ca4 --- /dev/null +++ b/apps/grist/grist-db-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: grist-db + namespace: grist + labels: + app: grist + component: database +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 20Gi diff --git a/apps/grist/grist-deployment.yaml b/apps/grist/grist-deployment.yaml new file mode 100644 index 0000000..71d92bb --- /dev/null +++ b/apps/grist/grist-deployment.yaml @@ -0,0 +1,127 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grist + namespace: grist + labels: + app: grist + component: app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: grist + component: app + template: + metadata: + labels: + app: grist + component: app + spec: + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h grist-postgres -U grist; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + securityContext: + fsGroup: 1000 + containers: + - name: grist + image: gristlabs/grist:1.7.19 + ports: + - name: http + containerPort: 8484 + env: + - name: APP_HOME_URL + value: https://grist.basicstack.de + - name: GRIST_DOMAIN + value: grist.basicstack.de + - name: GRIST_SINGLE_ORG + value: docs + - name: GRIST_ORG_IN_PATH + value: "false" + - name: GRIST_DEFAULT_PRODUCT + value: Free + - name: GRIST_HIDE_UI_ELEMENTS + value: billing + - name: PORT + value: "8484" + - name: TYPEORM_TYPE + value: postgres + - name: TYPEORM_HOST + value: grist-postgres + - name: TYPEORM_PORT + value: "5432" + - name: TYPEORM_DATABASE + value: grist + - name: TYPEORM_USERNAME + value: grist + - name: TYPEORM_PASSWORD + valueFrom: + secretKeyRef: + name: grist-db-credentials + key: postgres-password + - name: GRIST_SESSION_SECRET + valueFrom: + secretKeyRef: + name: grist-app-secrets + key: session-secret + - name: GRIST_OIDC_IDP_ISSUER + value: https://auth.basicstack.de + - name: GRIST_OIDC_IDP_CLIENT_ID + valueFrom: + secretKeyRef: + name: grist-oidc-secret + key: client-id + - name: GRIST_OIDC_IDP_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: grist-oidc-secret + key: client-secret + - name: GRIST_OIDC_SP_HOST + value: https://grist.basicstack.de + - name: GRIST_OIDC_IDP_SCOPES + value: openid email profile + - name: GRIST_OIDC_IDP_END_SESSION_ENDPOINT + value: https://auth.basicstack.de/api/oidc/end-session + - name: GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED + value: "true" + - name: GRIST_DEFAULT_EMAIL + value: andreas.leinen@basicstack.de + - name: GRIST_FORCE_LOGIN + value: "true" + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 2Gi + livenessProbe: + tcpSocket: + port: 8484 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /status + port: 8484 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 3 + volumeMounts: + - name: data + mountPath: /persist + volumes: + - name: data + persistentVolumeClaim: + claimName: grist-data diff --git a/apps/grist/grist-ingress.yaml b/apps/grist/grist-ingress.yaml new file mode 100644 index 0000000..50b991d --- /dev/null +++ b/apps/grist/grist-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: grist + namespace: grist + labels: + app: grist + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + tls: + - hosts: + - grist.basicstack.de + secretName: grist-tls + rules: + - host: grist.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: grist + port: + number: 80 diff --git a/apps/grist/grist-oidc-secret-sealed.yaml b/apps/grist/grist-oidc-secret-sealed.yaml new file mode 100644 index 0000000..c7f2b26 --- /dev/null +++ b/apps/grist/grist-oidc-secret-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: grist-oidc-secret + namespace: grist +spec: + encryptedData: + client-id: AgAAQEIKFFtfc3pm1yp3HKu7uIiTsYtcer6CYJbrUAY6bwNBE+Cj2NF3wvDVeW1M2padhXqyuuhuOIaYbbQ8NOgR3kVg6UIKgn5oNimpXgkwef4xCuGNbNN7kfRfVTJfwrJm8jON0nzmmvxnmsNsz7hJgRszQ2Tp1qJlCI/KIPsIZ0vcXehy+VWV5xBOQOnP+b/wxR+tbt9BnDX/acRi95JAJLXYabqkD7FDc0rLGiWmEsyl4prhhRcT2Ux7O6CJuyegAOOodPDsylkV2iyaI6L3EEY9IrrV5w4ta2tDDPEcnuLkUo/1LipfJUSGzO/tqYJMAbd6klTtfI3cIjimmQXUP/0uiy4YXT7LJud/yEwjSajRIMAdIMQNhAcXPM3EnW85nck4NYSO8qUgu04+lpPiQKiE1eSjCa/LWBIqOMG69nE/hZCCceNBa2krvJIhjrPewBEMxBvg4aCUUS9GO3YanoDN3Y5T9HcpjvbhtLvLMGu20uAKxw63m8DTyNnzruYkL4Qr/5KxyhquCGv9YuwJMtaiUlWc36mh6YKEyyiYSe56cMegUnIZKBWWjsJwJm+ApPKZSI3u9/AGiHodF9tKmqt8vCzuqyzhsAskQXpewdn3iHe2IY2KwuWPPoObUaaSYWcKdoEZYVgaY9IofXf/x4GbJ5GVqbrwCGFpXJGk3GFFtvlyFICINU+CCp5PXgoMiqvpNvUVEHVclCmcTMi8lWufWQi/6Xu4gH+08QHVMkzf55o= + client-secret: AgBrF2e/M48zFC0PdXm08IavK+qa3Sl3a0qmO0+eH/z5WYnRpgos5dFdCLxcrdDcJUgKy//pU+I23qoTXqgybxRnfVzsOdYb4UksGgWh9640BQ/NRuxqhdnfXjQN7PrJj+D+fumwRdTQU1PqyzEePM/6CVf6Ro9vdP6iTMDzbAnPnHoA8L8Hd+f3YKo43OKMR2GMwjNKAhpD20wMCYt1vTiEQX4drR9JW4mjcKfSCdaIrEFwIYn4EBkr1SwuC4Ek/KTn4CNQO82BQH+jjWO5CHRvLP6b4v2bVkgdQEqLR4+HpNjMm0JBKOjHvhfKv952T5mwROamWcpNlN4KW02YzIZMBU+dh7WhouZkDkQCalP4G7AGoLrjhWmLIgS56JqTuoiobFf90Uvz6ewcDmHAzPuYZMfn2mKndPYvoi/KSGzM3ceeNrFxpHCPhsG+UHbOfSCBmg7jqIeE9ial6fMsRRLeEqc3ay4lAN7PBXwJ/o3XMrezUy8RQsXLex9oS0IS9rktimwQNWEpY1adwWRERdBKqGAIEbVofVwai/SuC/dcgQsoX2r9kJOiA8ahM+eDxUg1534hb21v3tFWykp3y/EbFBxbPPtCsUY4ghZZ8O/lLtXsZdHxaq68RKbVqDbRGa/eLqCqStWS8Bk1XzwkJFBBHFcN9hDvNwoT+33QSSsuuiw8gLiztlSrewo33rVKEc9Enqb+f/uUX6HY9j3Iz7hvKZ2aYX/U8ayNN7hQaJryYQ== + template: + metadata: + creationTimestamp: null + name: grist-oidc-secret + namespace: grist diff --git a/apps/grist/grist-postgres-deployment.yaml b/apps/grist/grist-postgres-deployment.yaml new file mode 100644 index 0000000..22e5880 --- /dev/null +++ b/apps/grist/grist-postgres-deployment.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grist-postgres + namespace: grist + labels: + app: grist + component: database +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: grist + component: database + template: + metadata: + labels: + app: grist + component: database + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - name: postgres + containerPort: 5432 + env: + - name: POSTGRES_DB + value: grist + - name: POSTGRES_USER + value: grist + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: grist-db-credentials + key: postgres-password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + livenessProbe: + exec: + command: + - pg_isready + - -U + - grist + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - grist + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: grist-db diff --git a/apps/grist/grist-postgres-service.yaml b/apps/grist/grist-postgres-service.yaml new file mode 100644 index 0000000..53e22d4 --- /dev/null +++ b/apps/grist/grist-postgres-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: grist-postgres + namespace: grist + labels: + app: grist + component: database +spec: + type: ClusterIP + selector: + app: grist + component: database + ports: + - name: postgres + port: 5432 + targetPort: 5432 diff --git a/apps/grist/grist-service.yaml b/apps/grist/grist-service.yaml new file mode 100644 index 0000000..3cb8be4 --- /dev/null +++ b/apps/grist/grist-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: grist + namespace: grist + labels: + app: grist + component: app +spec: + type: ClusterIP + selector: + app: grist + component: app + ports: + - name: http + port: 80 + targetPort: 8484 diff --git a/apps/harbor/Chart.yaml b/apps/harbor/Chart.yaml new file mode 100644 index 0000000..48a64ce --- /dev/null +++ b/apps/harbor/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: harbor +description: Harbor container registry deployment for BasicStack +type: application +version: 1.0.0 +appVersion: "2.12.1" + +dependencies: + - name: harbor + version: 1.16.1 + repository: https://helm.goharbor.io diff --git a/apps/harbor/README.md b/apps/harbor/README.md new file mode 100644 index 0000000..1f43890 --- /dev/null +++ b/apps/harbor/README.md @@ -0,0 +1,71 @@ +# Harbor Container Registry + +Harbor is deployed at https://harbor.basicstack.de + +## Initial Access + +The initial admin credentials are stored in the `harbor-secrets` sealed secret: +- Username: `admin` +- Password: Retrieved from secret key `harborAdminPassword` + +## OIDC Authentication Setup + +Harbor requires OIDC to be configured via the web UI or API after initial deployment. The Helm chart does not support OIDC configuration at deployment time. + +### Steps to Configure Pocket ID OIDC + +1. **Create OIDC Client in Pocket ID** + - Navigate to https://auth.basicstack.de + - Create a new client with these settings: + - Client ID: `harbor` + - Redirect URIs: `https://harbor.basicstack.de/c/oidc/callback` + - Scopes: `openid`, `profile`, `email`, `groups` + - Save the client secret + +2. **Configure OIDC in Harbor** + - Log in to Harbor as admin: https://harbor.basicstack.de + - Navigate to: **Administration** → **Configuration** → **Authentication** + - Select **OIDC** as the authentication mode + - Fill in the following: + - **OIDC Provider Name**: `PocketID` + - **OIDC Endpoint**: `https://auth.basicstack.de` + - **OIDC Client ID**: `harbor` + - **OIDC Client Secret**: (paste the secret from Pocket ID) + - **Group Claim Name**: `groups` + - **OIDC Admin Group**: `admins` + - **OIDC Scope**: `openid,profile,email,groups` + - **Verify Certificate**: ✓ (enabled) + - **Automatic onboarding**: ✓ (enabled) + - **Username Claim**: `email` + - Click **Test OIDC Server** to verify connectivity + - Click **Save** to apply the configuration + +3. **Test OIDC Login** + - Log out of Harbor + - Return to the Harbor login page + - You should now see a "Login via OIDC Provider" button + - Click it to authenticate via Pocket ID + +### Reference Documentation + +- Harbor OIDC Configuration: https://goharbor.io/docs/2.12.0/administration/configure-authentication/oidc-auth/ +- Pocket ID Harbor Example: https://pocket-id.org/docs/client-examples/harbor + +## Storage + +Harbor uses encrypted Hetzner Cloud volumes for persistence: +- Registry data: 50Gi +- PostgreSQL database: 10Gi +- Redis cache: 5Gi +- Trivy vulnerability database: 5Gi +- Job service logs: 5Gi + +All PVCs are configured with `resourcePolicy: keep` to prevent data loss during upgrades. + +## Architecture + +- **Ingress**: Traefik with Let's Encrypt TLS certificates +- **Database**: Internal PostgreSQL +- **Cache**: Internal Redis +- **Vulnerability Scanning**: Trivy enabled +- **Authentication**: OIDC via Pocket ID (after manual configuration) diff --git a/apps/harbor/harbor-secrets-sealed.yaml b/apps/harbor/harbor-secrets-sealed.yaml new file mode 100644 index 0000000..922c41c --- /dev/null +++ b/apps/harbor/harbor-secrets-sealed.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: harbor-secrets + namespace: harbor +spec: + encryptedData: + database.password: AgCS/v9ajG58l1fF7cegx/pQLn4jBgJD3ixVDbQow2f0o6FvKXJK2na7IBdvaoE3rP5xO8BygQkU3z/Dq6TKKp5AuU1mcevkM5gIE2YRP4ZkXaChFhdGnZa6fAp1wU+K2rHKbGM2vNHUFXznHEogemD4IvSOJuiKJfx9ezUNCz+n3v1i3U/5R1j8CmJX/nXxzZRocMl0yJrSbBFsObU87MlO0I0L5pwQwoXRiW8WL3Dl9AJtd6EGvR8AvBQVYXzS3ke7F9mgC/fm4cTkfliHeGUUIylx48/Jr5zkdj6qNDr6q46+XzhKIzDkQrkY9fJVrmWeZoLhws5F9SpNWBbfwYSM266mlzWafEFIDjYIlgl4bGovnKceT6Ezh2ltPvQ6l/ppO95TH/A3HjzrSRH6Oo+rw4d+OE9skFz3KUgtOZHTFbEzAUiMSSL8g5T2AAyN1nG5JnJm4fx45RWR+JCbddtrBA9R+UwRfMYbYXYxCbqYBINgWzgeal0O2Qd7jnJROOZlLOZGPl4PwN8NNqfWfVTkw4we2/vDXzCG70/zS/pY0Q47lNAjdQOn8WakfuumT3PkhQM6739qeTM3no/Ga7xG9OmjvroDEokdRnhI4HZf4KcbdvtAZcsV3KD6WoHhTULtK8C/4+cb/Bj1QIz/UQ0/ZF2oahLpCwQyDRxBOUfZB/G1PMPZbwg8LkgCKit1RDGOVuMD25TOKhm9W7ONsJrJ55h+pcNBc7/TcNv4mtNMlWh8dYfMFeKsyCw8vg== + harborAdminPassword: AgBTLxpA5ygiVNj5ADtI1/2w2fSdbYPwBOVmCkj7FodimXl5vCoNKnd0rO7t8SPW+1SDDvQsOjekhWaqkMEIm4rLtRpRU1TWWuVG6UkWID3aBwWBqOafMD11Wvq/uQrlYuQLmj2pKPRZxfODCWUxNEC9vZXAucCL8EzXlgYXy+P0ZX3vbj4/nnnPDYC4vfxl0/dLdVubNTfoBhZ8GA2waPVHuQRZKJCqFaHt2RAdXFzuGmuu0jIa7bf3Izr2CzAKkhz515E4TOtDitFNQOJExUCf/14NqKp142s1/4KtB9iuUSYodswKyJGtAtVGRPIGjkYB1a37RFbqVvP3RN5Bx99cH8Wdj816l4y+Qq0mvg/arClLggOtDZZKMsd5QXaPkTVlyBKItNhfDjDgk+G8rzzNwspN73BS6CRS6tWu1BGufsWmnfm/vSCdkw/EjuoPj5LOyWkPdhrJxK+dgvr5zYfw+/mISXucMy4SQDFuSbp9reBqE329nJolyUuRQFxx2hGKm7KIB0vFRi62cH2mBmfG//d79eq/4hSCPMNola8SJ0neWvvaJkR491LLN5Pus9zFcOHNSy/i+lpXs19ZMacLeL/X4yuq7neiFTaMtKIcJ/rQSwGxf9lDcC2jadi3AlSmRP6Va3XwNm7FEP83bRUV5pScLlnwY+EqgB7GdAC+2Bd+Ls3Hb5mc2JoNZnpZi9A1Y+108NWtvnjBMbFwp/seHXZqRUwrlhNq2R/cXdPRJLVnY2U+NRtEYTfivg== + oidc.clientSecret: AgCNjio841Vsf+UysN38gKTD+E1QknD5Wmsfp3IKoqvmChI3brEfTSjb4pROAcSscVnC/WHQbl73J1PJIj8YPs7173nn8gJDBnoIIjlVM3p33RdzWwljgb8MRCFXZ2fjimYE2DwcdIy3Izcyf/6m2w01xIcNY4GpHcKpcwA9LoZ/oGxzkPrZx7FOmDKpi3mN27eBfKbhvpVuIL+KhT40z1G8F3C6khA5cg2hiyPbQgWhcbDLhOCUZCTcqKeUJ7VWwYS1D9kNE05V+rWJJqfV/jvjHJ12ajwUSSkpHG5fjPq1eVuikkCzhXZyxSAocmtMt5Jspve1dz5AqEiw5EkH6Tb4EVfLYqCY6IJH9LAs1+8+8ETrszMRdCEZLSQhcAWXNVbURPcoJRUeSI9qQMBECdG7QVP0S6VQ8hAMadtJ1VnDHcF1lLFKKLDPLiNvnqz0FlO4gCYH193gNbfOqoSQkidwpzVX0aRWZUyBwMbS9umbrFrvZQ/hzj7U+7SIxVHoYvLAigw8gzy3ZyA8qkttddayZlO9pDvSBlNW6QbdTVn9Iag4rjgpkBvXv5MTHUR4S4ICO5LDzAyPmkxmMpcl+gYd/xEuI59c7q+GMQCHyTMi8UpIX2zbPKa4WROFBb684dgOh2dZyYO4+Ywbaj99chXX6AtWLIBVHfVcVXYIZVgduqjNkCfmGUuV+gTOBDbu97RLOJ6p6woWZ9j9yDWOTBmA1npisiTr30da0fJWQRgFag== + template: + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + creationTimestamp: null + name: harbor-secrets + namespace: harbor + type: Opaque diff --git a/apps/harbor/values.yaml b/apps/harbor/values.yaml new file mode 100644 index 0000000..80c149d --- /dev/null +++ b/apps/harbor/values.yaml @@ -0,0 +1,121 @@ +harbor: + expose: + type: ingress + tls: + enabled: true + certSource: secret + secret: + secretName: harbor-tls + ingress: + hosts: + core: harbor.basicstack.de + className: traefik + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + + externalURL: https://harbor.basicstack.de + + # Hetzner Cloud Volumes are ReadWriteOnce; RollingUpdate deadlocks on + # jobservice/registry rollouts because the new pod cannot attach the PVC + # while the old pod still holds it on another node (DEV-458). + updateStrategy: + type: Recreate + + persistence: + enabled: true + resourcePolicy: keep + persistentVolumeClaim: + registry: + storageClass: hcloud-volumes-encrypted + size: 50Gi + database: + storageClass: hcloud-volumes-encrypted + size: 10Gi + redis: + storageClass: hcloud-volumes-encrypted + size: 5Gi + trivy: + storageClass: hcloud-volumes-encrypted + size: 5Gi + jobservice: + jobLog: + storageClass: hcloud-volumes-encrypted + size: 5Gi + + # Use internal PostgreSQL and Redis + database: + type: internal + internal: + existingSecret: harbor-secrets + # Fix PostgreSQL data directory permissions + # The init container needs to set correct ownership for PostgreSQL + initContainer: + migrator: {} + permissions: + image: + registry: docker.io + repository: library/busybox + tag: latest + # Set proper security context for PostgreSQL + securityContext: + runAsUser: 999 + fsGroup: 999 + fsGroupChangePolicy: "OnRootMismatch" + + redis: + type: internal + + # Harbor admin credentials + existingSecretAdminPassword: harbor-secrets + existingSecretAdminPasswordKey: harborAdminPassword + + # OIDC authentication via Pocket ID + # Note: Harbor requires database_auth to be configured first, then OIDC can be added via UI + # See: https://goharbor.io/docs/2.12.0/administration/configure-authentication/oidc-auth/ + + # Resource limits + core: + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 1000m + + portal: + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 256Mi + cpu: 500m + + registry: + # Skip Kopia fs-backup of the registry blob PVC (DEV-573 safety net). + podAnnotations: + backup.velero.io/backup-volumes-excludes: registry-data + registry: + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 1000m + + trivy: + enabled: true + # Skip Kopia fs-backup of the trivy scan cache PVC (DEV-573 safety net). + podAnnotations: + backup.velero.io/backup-volumes-excludes: data + resources: + requests: + memory: 512Mi + cpu: 200m + limits: + memory: 2Gi + cpu: 1000m diff --git a/apps/headlamp/deployment.yaml b/apps/headlamp/deployment.yaml index 68a5e74..26eb220 100644 --- a/apps/headlamp/deployment.yaml +++ b/apps/headlamp/deployment.yaml @@ -16,7 +16,7 @@ spec: serviceAccountName: headlamp-admin containers: - name: headlamp - image: ghcr.io/headlamp-k8s/headlamp:v0.43.0 + image: ghcr.io/headlamp-k8s/headlamp:v0.45.0 imagePullPolicy: IfNotPresent args: - "-in-cluster" diff --git a/apps/kaneo/README.md b/apps/kaneo/README.md new file mode 100644 index 0000000..212ae3d --- /dev/null +++ b/apps/kaneo/README.md @@ -0,0 +1,61 @@ +# Kaneo + +Self-hosted [Kaneo](https://github.com/usekaneo/kaneo) — open-source project +management — deployed on the BasicStack Kubernetes cluster. + +- **URL:** https://kaneo.basicstack.de +- **Namespace:** `kaneo` +- **Distribution:** hand-rolled manifests, GitOps-managed by Argo CD + (see `apps/app-kaneo.yaml`). +- **Auth:** OIDC via Pocket ID (`https://auth.basicstack.de`) with + `CUSTOM_OAUTH_*` env vars. Client id + secret live in + `kaneo-oidc-secret` (SealedSecret). Access is restricted to + Pocket ID groups `kaneo-admins` / `kaneo-members`. +- **Database:** dedicated PostgreSQL in-namespace on + `hcloud-volumes-encrypted` (20Gi). Both the Postgres and Kaneo + pods pin to the `fsn1` datacenter via + `csi.hetzner.cloud/location=fsn1` node affinity — the Hetzner + volume is DC-local. +- **TLS:** Traefik ingress, cert issued by `letsencrypt-prod` + (DNS-01 via the Hetzner cert-manager webhook). +- **Image:** `ghcr.io/usekaneo/kaneo:2.22.0` (single container + bundling API + web on port 5173, health at `/api/health`). + +## Files + +| File | Purpose | +| --- | --- | +| `kaneo-postgres-pvc.yaml` | 20 Gi encrypted Hetzner volume for Postgres data. | +| `kaneo-postgres-deployment.yaml` | Postgres 16-alpine, pinned to `fsn1`. | +| `kaneo-postgres-service.yaml` | ClusterIP service `kaneo-postgres:5432`. | +| `kaneo-db-credentials-sealed.yaml` | Postgres user password (SealedSecret). | +| `kaneo-app-secrets-sealed.yaml` | `AUTH_SECRET` (Better Auth JWT secret, ≥32 chars). | +| `kaneo-oidc-secret-sealed.yaml` | OIDC client id + secret from Pocket ID. | +| `kaneo-smtp-sealed.yaml` | SMTP submission credentials for `kaneo@basicstack.de` (SealedSecret). | +| `kaneo-deployment.yaml` | Kaneo app deployment (pinned to `fsn1`). | +| `kaneo-service.yaml` | ClusterIP service on port 80 → container 5173. | +| `kaneo-ingress.yaml` | Traefik ingress + `letsencrypt-prod` cert. | + +## Post-deploy setup + +1. Point browser at https://kaneo.basicstack.de and sign in via the + "Sign in with Pocket ID" (custom OAuth) button. +2. The first admin (`andreas.leinen@basicstack.de`, member of + `kaneo-admins`) must be promoted inside Kaneo — the current + upstream release does not map OIDC groups to roles. + +## SMTP + +Outbound SMTP is wired to the Stalwart submission relay at +`mail.basicstack.de:587` (STARTTLS) using the `kaneo@basicstack.de` +mailbox. Credentials live in the `kaneo-smtp` SealedSecret +(`kaneo-smtp-sealed.yaml`) with keys `smtp-user` and `smtp-password`. + +SMTP is used only for workspace invites and notifications; +`DISABLE_EMAIL_OTP_SIGN_IN=true` remains set so users still sign in via +Pocket ID SSO, not email codes. + +To rotate the mailbox password: update it in Stalwart, re-seal +`kaneo-smtp` with the new value, commit, and let Argo apply. The +existing pod picks up the new secret on next restart +(`kubectl -n kaneo rollout restart deploy/kaneo`). diff --git a/apps/kaneo/kaneo-app-secrets-sealed.yaml b/apps/kaneo/kaneo-app-secrets-sealed.yaml new file mode 100644 index 0000000..7c5cb17 --- /dev/null +++ b/apps/kaneo/kaneo-app-secrets-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: kaneo-app-secrets + namespace: kaneo +spec: + encryptedData: + auth-secret: AgArCARyFt7NinaeQOfDDEpTKKWVba8IlKh7NN/vflgdOkE5bjz/P/ZzGUVhUOTyqCs+3dU+hX6sqc7IYbYckXdprwKJSXy47HLEsZ/cuJ0FRIBShRyXm+I62Yu8WWLHNJLTnlWyfirBC6Pg65kXckoKiYPwcVLSD5I+YHzo3Dt3Hl6MeW/iVocM9xgjBD/5yRt1ag+S4JYv0t1y39Cz80XxbFvo55RzYWr3OlYNI325b9W/d5uylqlDemln8NymL5iCdfnLDZoc+1r+eHAf1HDP6Uch1dG+c/xsrlvoWpzyMeSEbEY1tGeYFZ1oKm4B1pyUH9dfdxWEaNThheL5Xw2QX/klpZdTq5Jl6Lfxme5aLyqNtP9J6ovd5b1oVYW1DWc+RAqBPhFlHnkfc+8ZeP6vrmnUULq0ssSVqpXOz4zLgDHU61UzSllNdpZWXokigranj3dq5CM7WkYLEj9kfdDIGS3gDUD+EmIkIj/N96URNpOINSwFVAcnE73k3ij9mZWECLl++lzh1fqHrdYQiTI6A7UpxPudc7BoujBQFqGhUDXGdzBWsrqr11tldD+kb/kcKQYxs6sFHyw0pDgCLVZukPv7TWRNfVRYUv68h/1ZKAwmj8KkuGlNtAQ+NyvUWyct3SA+pQYWabr33pIPekWPIXMIty6/zfKu92hLFglwkauewxqLtEBZ6FFukKMyyfowV+iwDIMyY3//MvxiRzxD7sc5sLwcfvPCBzd9XGaJO1ZLX6r43gfOu6HFHiq4FqdjYJ9fqG3zUKeo5bnav7E6 + template: + metadata: + creationTimestamp: null + name: kaneo-app-secrets + namespace: kaneo diff --git a/apps/kaneo/kaneo-db-credentials-sealed.yaml b/apps/kaneo/kaneo-db-credentials-sealed.yaml new file mode 100644 index 0000000..343ed5a --- /dev/null +++ b/apps/kaneo/kaneo-db-credentials-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: kaneo-db-credentials + namespace: kaneo +spec: + encryptedData: + database-url: AgDEiqPdexD58tc4Sv63mk1erIHAeweJzGpKgoOcro6ioW4eX9pWnfVnVfmtsqQK8WXkRuEP5pAnj1LZAhuo6hfwk/lOpoYItqZKl6KGK0EQZ3u/hcmUt0yeEkTJuDPGBbgk1RuT1G2u2uDDYYJYHmAQtIIFbAoeJS2iDKnKkl6UHZdEJhO10w6IHGiNCT6Z9NovLJau5cw9tTv7xnbGO8F1QqwLh/dL0kI2oYNTYg1RxpegXRFeKGz+QSJ3rbxlH2P/E01Fevma2/ZhOBjALS1bpW9R6pgQm4n/FDu/fu72T62yw2oNvj6nIeRtIsm9ze02jQqYwF7mgct6IJOdqY7RoYnTZJQ+/Xl/iiBIGpzXan9bU/JZw6sEeyxS+tyeaolz4oiTeNfRXp8QNdX3qbXM9OE5Q+bjqx1q+cZcnFcBIvGK8vGqlWwDjwNicucuNBZuv4JWEOp2N8axoFVRUsUn/Frv7McqKT1Q2mRXf0aiGYh9m7EIdnFAx3JeiBJv392jBWcwvJpQ+J0LIsh/bJEBtdC4c16Y+38nbqfjYEUJWVIoj9SvJEo3QZPitUz99cwdtDn+cOVVYp+lv5BiunM7OgvAgBbX7F4Ri/QlL8rt3/8lO0Augwp933lSTuGGnMCrXPz2QCagI6B0l8F5q5OTnljgR7JXslyGFaemtj9jZEnCiSsw9KAiDdOUyk36CJSfHmRedJJ6o2nalb3zdFDF7iMXxz6fpTQK0QQGAccO518zUHOpb3Jb6XbOBKDgTBOWPEGlroiavrOtoRDJ+B5nR/N4c3Indpn7d0TTDU/4rz4V1uNkRrHnA26ya9Q= + postgres-password: AgBo5f/NzqZ/9jsOzXmoivPVVeLA3xk9GWocQQlLv34Rc1K5fzhSoCYdy8o7k06R83VeNsJylxfAsOI7HNIo0xWUPqC8HnbW482ajtREh93wOFCaZzjKu8ClbFIkvTb19GRCPLSmzBMFsTCJzjSa5rWGqK2Ezj7LFeTTiehsqS/PbPUYi6w4X6ZnChS3VuMXJVJAvbCF8fFS3x/YuuJVjjd+TmVsPmGOa6J+r/6RG7l4DsR+SmV3HQ7M6bLqTsUPjzg5Foz11yc8ER8xQQTgbbfrBPwgJQoTX622yHf0Q313ns7vyiKGMqge9DSQvDmGmMDmAxqtYInycVBH1CwmfKVbw3CyFGt0D5LJXiBaTE89uLErsNCQCK6NKAODqffKlOWpZ1KVF+ZIQ6nqJETtj49uqdKGowauCs3tDl/Hi82+hxycEVMWUwgu81CJ6GT7+vSZfz4+9FRbLoXlbdfR/duG14f+AkPirikeDcy3bFy5XiBgPVQXL0EbAY8TaPkYu+TS1Roo2F6Gqj3D1+i4GapVodrBntl1gMPvMnd3dcGGl/VzREGZvRsZgdRprFi+nGPmotRhIO3eGcPautlZ5m0WMqKCpA4jW0sG1sgkVwRpn2Zdq4Uw2+IKNHTkJKclIXCaw2/b6WNCmiwbe1ssFoR/QgikzshshoB5XpIYFe7Jr5Sj9mmNH6L4PpCIZ/sCe8MF5eMD8IuyWSVmflID07sfBD6GL3XB9TFLzSyviHE/VJdGY7l+ZeR64UZZ1ti4bwI= + template: + metadata: + creationTimestamp: null + name: kaneo-db-credentials + namespace: kaneo diff --git a/apps/kaneo/kaneo-deployment.yaml b/apps/kaneo/kaneo-deployment.yaml new file mode 100644 index 0000000..fda2be1 --- /dev/null +++ b/apps/kaneo/kaneo-deployment.yaml @@ -0,0 +1,157 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaneo + namespace: kaneo + labels: + app: kaneo + component: app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: kaneo + component: app + template: + metadata: + labels: + app: kaneo + component: app + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h kaneo-postgres -U kaneo -d kaneo; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + containers: + - name: kaneo + image: ghcr.io/usekaneo/kaneo:2.22.0 + ports: + - name: http + containerPort: 5173 + env: + # Public URL of this Kaneo instance. + - name: KANEO_CLIENT_URL + value: https://kaneo.basicstack.de + - name: CORS_ORIGINS + value: https://kaneo.basicstack.de + # Better Auth JWT secret (>=32 chars). + - name: AUTH_SECRET + valueFrom: + secretKeyRef: + name: kaneo-app-secrets + key: auth-secret + # Database credentials. DATABASE_URL is preferred; POSTGRES_* are + # accepted as a fallback and useful for the built-in migrations. + - name: POSTGRES_DB + value: kaneo + - name: POSTGRES_USER + value: kaneo + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: kaneo-db-credentials + key: postgres-password + - name: POSTGRES_HOST + value: kaneo-postgres + - name: POSTGRES_PORT + value: "5432" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: kaneo-db-credentials + key: database-url + # Self-hosted: keep public registration off, log in via OIDC only. + - name: DISABLE_REGISTRATION + value: "true" + - name: DISABLE_PASSWORD_REGISTRATION + value: "true" + - name: DISABLE_EMAIL_OTP_SIGN_IN + value: "true" + - name: KANEO_CLOUD + value: "false" + # Custom OAuth / OIDC via Pocket ID. + - name: CUSTOM_OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: kaneo-oidc-secret + key: client-id + - name: CUSTOM_OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: kaneo-oidc-secret + key: client-secret + - name: CUSTOM_OAUTH_DISCOVERY_URL + value: https://auth.basicstack.de/.well-known/openid-configuration + - name: CUSTOM_OAUTH_AUTHORIZATION_URL + value: https://auth.basicstack.de/authorize + - name: CUSTOM_OAUTH_TOKEN_URL + value: https://auth.basicstack.de/api/oidc/token + - name: CUSTOM_OAUTH_USER_INFO_URL + value: https://auth.basicstack.de/api/oidc/userinfo + - name: CUSTOM_OAUTH_LOGOUT_URL + value: https://auth.basicstack.de/api/oidc/end-session + - name: CUSTOM_OAUTH_SCOPES + value: openid,profile,email,groups + - name: CUSTOM_AUTH_PKCE + value: "true" + # SMTP: outbound only, used for workspace invites / notifications. + # Sign-in still requires OIDC (DISABLE_EMAIL_OTP_SIGN_IN=true above). + - name: SMTP_HOST + value: mail.basicstack.de + - name: SMTP_PORT + value: "587" + - name: SMTP_SECURE + value: "false" + - name: SMTP_REQUIRE_TLS + value: "true" + - name: SMTP_USER + valueFrom: + secretKeyRef: + name: kaneo-smtp + key: smtp-user + - name: SMTP_PASSWORD + valueFrom: + secretKeyRef: + name: kaneo-smtp + key: smtp-password + - name: SMTP_FROM + value: "Kaneo " + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + livenessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/health + port: http + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 diff --git a/apps/kaneo/kaneo-ingress.yaml b/apps/kaneo/kaneo-ingress.yaml new file mode 100644 index 0000000..de218c1 --- /dev/null +++ b/apps/kaneo/kaneo-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kaneo + namespace: kaneo + labels: + app: kaneo + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + tls: + - hosts: + - kaneo.basicstack.de + secretName: kaneo-tls + rules: + - host: kaneo.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaneo + port: + number: 80 diff --git a/apps/kaneo/kaneo-oidc-secret-sealed.yaml b/apps/kaneo/kaneo-oidc-secret-sealed.yaml new file mode 100644 index 0000000..1854e4a --- /dev/null +++ b/apps/kaneo/kaneo-oidc-secret-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: kaneo-oidc-secret + namespace: kaneo +spec: + encryptedData: + client-id: AgBGmlu+mGYlHcWPCRHZ3IbevGB5kGntqPasFBnny6ZydTeCmN8NWpy0bmoorecJSbvdeP38UDgfEjtmusfkipnrlfsZd7BugZLiijq5+T+9al5JTl/UChMl6u/gReaVPgLi9iketDN6oB2XC/56mV+MdMb/bO4d1u3CRFUJ/Pq1znWeQpVsXO1yOLoOge4+PYDYjgd4O6mJsbAQcmGxQ7pwcByw//+50Mvuw0gJBqI5ZSGKd0jSNnaVcECtvjY6v3xN1NCeVF1FoWKl4s7o/Nrq5mDltW/y6g0h3N1Fzan/D47UVQjGLYu6cTmM7ReqToYk65cznttGkyXfqrlVL/oEiYENtaqp/BZvwPBMKEAjJsufwGvbNgsxa3eGs+p3L+HwzdgTQbVGu2wVp+TXW8zwZvkDyprUU91Fu6QFsspV/GRYgqVmp4RxrJ0PzZInp4ZhLt/zOyWzbEkILMPV1A5fzPjrPmwpmhln/K0VTdpRN62XQRgH0P7EP6Ao0sP2/ae39eTkGlhb5fwzhSqK3LSmBYmoXNcffA4nBLFohSndmwgoxzZXM1FJ1kXl4wuevU8Eh8BD3dVIZkuYacKiIUFoNn74NbPtJpl3/MWfXef9Eb7IBR8KyurbdwSmKSAZDqjmeGTa271+KUiGfST+FYQgSDfFfQQEmh+K49WqqbOiod4Mh5RTGWJx3WNwEONsTRKVjiGgLw4A+ENNDB39PoRChoRoRRogg+ZJZE27EjxbmrKIjrw= + client-secret: AgBJQi6fIlTkOIZa1zr9jjyNOFAyZUjHWy0ZBjfcNh+iY2V3qIpG8kNPBntmvtVtemYI+UxEvvlalq0iEue+AtVmUoJeeO/lYuEDv+BrJGUWeZITbItuyoG+xGZM4x41X6FPgfRcnLCfii3dmwOeDZh1asR2Ahy2szdXrd2QaDeZMCCXNR+IeuP9U+IqJncfm+KjCqPABVbsZuGfDrT7r4XRv4mHQGnILD4IyA9N0/XyfY/cZRJryWeF5xbrOAP/ZWJduqYGIm2ahJfRZWIzk1Ujknq35iJ4bOZiw5eWb+aYhD9cxJj2UMBwWdqnXnpH+p55lrBqWlCzI0yDOfk4UQCI2RzQBJc/RvVS0z16k7El4dl9lOFTcgwBg13C/JC8Y0wo4M1jEap2ty/2ifwVZBZdflT8Uy5sk8dKBOv9wCupA/N60ACwlsavCUlgtQetVZUHIlI1U1rKCzArpGZ6PuREZokjdHw01u/j9ANEt1AaMe3G5jLcGohyvpcgL+zqI2S0rCqbSdRqWzZvr2kM8uFoeZSJLG3y4wdwZMgvRGMfoAKD1YlUGuhWeeZTda77ScM6ii8iEZEjvIgJXrfSCUf1vJ4QIAdMnNi05d3pTTDVHNTRjWBrklVb6oTpe/rFF1BBP6zhwGylWtb71xszbQyX8lzD+7v1O4iXr7ZWo86L9mGKz6FMnj0bO4LLVoqeVMy5DHo2fCuplix03P3WQCyjGKranAWdLalEV59QdnEhaQ== + template: + metadata: + creationTimestamp: null + name: kaneo-oidc-secret + namespace: kaneo diff --git a/apps/kaneo/kaneo-postgres-deployment.yaml b/apps/kaneo/kaneo-postgres-deployment.yaml new file mode 100644 index 0000000..8031a00 --- /dev/null +++ b/apps/kaneo/kaneo-postgres-deployment.yaml @@ -0,0 +1,83 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaneo-postgres + namespace: kaneo + labels: + app: kaneo + component: database +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: kaneo + component: database + template: + metadata: + labels: + app: kaneo + component: database + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: postgres + image: postgres:16-alpine + ports: + - name: postgres + containerPort: 5432 + env: + - name: POSTGRES_DB + value: kaneo + - name: POSTGRES_USER + value: kaneo + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: kaneo-db-credentials + key: postgres-password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + livenessProbe: + exec: + command: + - pg_isready + - -U + - kaneo + - -d + - kaneo + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - kaneo + - -d + - kaneo + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: kaneo-postgres-data diff --git a/apps/kaneo/kaneo-postgres-pvc.yaml b/apps/kaneo/kaneo-postgres-pvc.yaml new file mode 100644 index 0000000..1ae75e4 --- /dev/null +++ b/apps/kaneo/kaneo-postgres-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: kaneo-postgres-data + namespace: kaneo + labels: + app: kaneo + component: database +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 20Gi diff --git a/apps/kaneo/kaneo-postgres-service.yaml b/apps/kaneo/kaneo-postgres-service.yaml new file mode 100644 index 0000000..f92daf1 --- /dev/null +++ b/apps/kaneo/kaneo-postgres-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: kaneo-postgres + namespace: kaneo + labels: + app: kaneo + component: database +spec: + type: ClusterIP + selector: + app: kaneo + component: database + ports: + - name: postgres + port: 5432 + targetPort: 5432 diff --git a/apps/kaneo/kaneo-service.yaml b/apps/kaneo/kaneo-service.yaml new file mode 100644 index 0000000..c56b8be --- /dev/null +++ b/apps/kaneo/kaneo-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: kaneo + namespace: kaneo + labels: + app: kaneo + component: app +spec: + type: ClusterIP + selector: + app: kaneo + component: app + ports: + - name: http + port: 80 + targetPort: 5173 diff --git a/apps/kaneo/kaneo-smtp-sealed.yaml b/apps/kaneo/kaneo-smtp-sealed.yaml new file mode 100644 index 0000000..925407b --- /dev/null +++ b/apps/kaneo/kaneo-smtp-sealed.yaml @@ -0,0 +1,16 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: kaneo-smtp + namespace: kaneo +spec: + encryptedData: + smtp-password: AgByMIhrSeQbPJezoqPf4jtTCx/b/R+TGZPHjjvUcdZJimgZY8XO72BA2nI/419UWqY3rBmWyquYMnEFJT0/48Rn0Nk4aFxBwBIxVuiG9y1+f5yXIhT2JuOqGoBnImYezCCtYOcgn4Byo6Twldce1ElRCqwH6grsVOLKtogZvLJC0sz6EWbqPfLrDuGyOFz2wg5P3Cjmy8W9ZgcTDTpVI4JxZh2JKbIJpq4kGDQD0miW8Av0BOmSi75uvT2PvaV/uinJ72VJFLP/GTHWjf/9kpOiIC2TL4B+Kk0HtMeWSzNF79itsD53ow7bya/bq//zUiwdaIeSaAcSJL8XqL9q38LIEL0Vr9ccXN9xsPZttLw836NDeLsryE+RhF3pIK2vEs76YIeO03AEnXNjEYOcwy7ePGYh6Y/Uf/B75QNzbzrq8H4B5lKFVmQgTBLJnvxTzOc6Sd/Yssgl1wJw90jgw2HYcLHXt/eQrB4cGKWSwrh2x6fnU79UpSGatdTqIyP2Q8vbgKmxqqmg6NDzjgeatT6ADKOqJKAjnXfrr8Bm3NjvAVWOGWuIfWdS0wHiEOhAoUNNAQ+bD7f/SbJE24q8W6juhzTmRLuU1BM2K51YPHA3BqyEpzygaboSi/bgHVOuoIAEAt7jAM6mbW5RQBwiNglF/C2Oem8J3LOdDlndJoSMaWKm5p1lKc3M59jr47jaDXm9HKM4ypC58cqty3varqxBwwdZHirZ6No0Ar4Sd78I4jo4kXk9zu33UjCcR/X2NX1abIYZ8eNoUga+I9AOkpM= + smtp-user: AgAq1mT9fzt9/VHmcbn0Yzr/8HWadEBe1XjJOTonao7Tdc3EO1PvCgEE9nBkrqOK5x2uDP1jZuP98XxJXr6mHuCnEOn8SHMN93TqK5rLhsKir1ImdNWKC+ruVI3TPQfTkeEz5m3u0DjHWSymva7BjmTIIW5gE4SDt/7WOFrsWbX1fLqQKfuB/k1qIBd+rFL7rVSe3XnRzxxwwrIbYfczSCknMbOot65FRcR+pC6ox7hOYC7pMIU76WMrVYx0cmDfAZAidcOQP9+K/H4LY0vkwPnaNk7+Is5gJifWR37lTnCkjRknMWB+yyDZF3BkmmdHNvXE6OlMcBF8l/jrO7S0rTtkCDaXllpRt1tWoSY2Kyr2DTaLt9QQvd1v8Rz2mw1qeDZg0mO/BswPF10SEKwjD3RlGFkA6mK9hZE5G+eDpgMH9u5HEKftI6jD/wdzElY+GdEFhlmj/ATZuNk+oC+eVnHGHUnY39lQYX+7PEtOujsKAjsuCPBaMDMkTP+f+LOUguuaRaanpLctmRAnT5BK19dmNuJtZPZAPgzCLu44884BH1qpKpVXkaUZSUbYA1RpKflcf1+uWJaqp7oBOZLSVoVSLiVG8ypGv7IPeTiILQcWQK27z6H0VxiAZ0S8yzNcCmeTWg8/wUGVzk0VJ9imPIShpiq515X6GuU1v1LsMiuJtFfL3EpNdDln04dLFWVb0ghIdt4Hn4U3IpJRIcP+XEEpv4+6 + template: + metadata: + creationTimestamp: null + name: kaneo-smtp + namespace: kaneo +--- diff --git a/apps/monitoring/README.md b/apps/monitoring/README.md new file mode 100644 index 0000000..15f5a4e --- /dev/null +++ b/apps/monitoring/README.md @@ -0,0 +1,57 @@ +# monitoring — backup CronJobs + +Manifests recording the cluster-side monitoring backup CronJobs that were previously applied out-of-band. These files are the authoritative source (`kubectl apply -f apps/monitoring/`). See [DEV-464](/DEV/issues/DEV-464) for the repair context. + +- `backup-loki-restic-cronjob.yaml` — daily restic backup of `loki-storage-encrypted` to `hetzner-s3:${BUCKET}/restic/loki`. Co-schedules with the Loki pod via `podAffinity` (RWO permits additional read-only mounts on the same node). Deployed per the [DEV-482](/DEV/issues/DEV-482) Option 4 rollout ([DEV-485](/DEV/issues/DEV-485)). +- `backup-grafana-restic-cronjob.yaml` — daily restic backup of `grafana-storage` to `hetzner-s3:${BUCKET}/restic/grafana`. Pinned to `k3s-worker-2` via `nodeSelector` (the local-path PV anchors the grafana pod there already, no `podAffinity` needed). Schedule `15 3 * * *` — offset from the loki run at `03:00`. Deployed per the [DEV-482](/DEV/issues/DEV-482) Option 4 rollout ([DEV-486](/DEV/issues/DEV-486)). +- `prometheus-backup-cronjob.yaml` + `prometheus-backup-sealed.yaml` — daily restic backup of `prometheus-data-encrypted` to `hetzner-s3:${BUCKET}/restic/prometheus`. Co-schedules with the Prometheus pod via `podAffinity` so the RWO PVC attaches on the same node. Schedule `30 3 * * *` — offset from the loki (03:00) and grafana (03:15) runs. Migrated from the DEV-465 `rclone sync` job to restic client-side encryption per [DEV-492](/DEV/issues/DEV-492) / [DEV-482](/DEV/issues/DEV-482) Option 4. **Compaction-race mitigation:** `--exclude wal/*` + `--exclude chunks_head/*` + accept `restic backup` exit code 3 (source file vanished mid-walk) as a warning, not a failure; restore drill re-runs `promtool tsdb analyze` per block. +- `backup-restic-alerts.yaml` + `backup-restic-alerts.test.yaml` — `PrometheusRule` with freshness, integrity, and repo-size alerts covering the three restic repos (loki/grafana/prometheus), plus a `promtool test rules` unit test proving each alert fires against synthetic samples ([DEV-490](/DEV/issues/DEV-490), extended in [DEV-492](/DEV/issues/DEV-492)). The `k8s-resources` restic repo was superseded by Velero cluster-reconstruction backups and retired in [DEV-576](/DEV/issues/DEV-576). + +## Shared SealedSecret + +All four restic CronJobs read Hetzner S3 credentials + the restic repository password from **SealedSecret `monitoring-s3-backup`** (namespace `monitoring`). Keys: + +| Key | Purpose | +|------------------|------------------------------------------------------------------------------------------------------------------| +| `access-key` | Hetzner Object Storage access key ID | +| `secret-key` | Hetzner Object Storage secret access key | +| `endpoint` | S3 endpoint hostname (e.g. `fsn1.your-objectstorage.com`) | +| `bucket` | Bucket name (single bucket, per-prefix repos) | +| `restic-password`| 32-byte random string sealed at [DEV-484](/DEV/issues/DEV-484); plaintext copy in Passbolt entry `restic / monitoring backups`. Rotation: `restic key add` → seal new value → `restic key remove` old id. Same key protects all three remaining repos (loki/grafana/prometheus) — rotating rewrites the key file on every repo. | + +## Restore + +Restore procedure for all four restic repos: [`docs/monitoring/restic-restore.md`](../../docs/monitoring/restic-restore.md). First drill (2026-08-16, loki/k8s-resources) passed — see [DEV-488](/DEV/issues/DEV-488). Prometheus repo added and drilled same day — see [DEV-492](/DEV/issues/DEV-492). + +## Emitted metrics (textfile-collector format) + +Each restic CronJob writes `/metrics/backup_.prom` (atomic — `.prom.tmp` + `mv`) into a `hostPath` volume mounted at the node-exporter textfile-collector directory (`/var/lib/node_exporter/textfile_collector`). The kube-prometheus-stack node-exporter DaemonSet has `--collector.textfile.directory=/host/textfile_collector` enabled ([DEV-494](/DEV/issues/DEV-494), applied via [`apps/observability/patches/node-exporter-textfile-collector.yaml`](../observability/patches/node-exporter-textfile-collector.yaml)) and surfaces those samples in Prometheus. + +| Metric | Emitted by | +|--------------------------------------------|----------------------------------------------------------------------------------| +| `backup__success` (0/1) | `backup-{loki,grafana}-*-cronjob.yaml`, `prometheus-backup-cronjob.yaml` | +| `backup__timestamp_seconds` | same | +| `backup__check_status` (exit code) | same — from `restic check --read-data-subset=5%` | +| `backup_prometheus_backup_status` | `prometheus-backup-cronjob.yaml` — `restic backup` exit code (3 = accepted compaction race) | +| `restic_repo_size_bytes{repo=""}` | same — from `restic stats --json --mode raw-data` (added in DEV-490) | + +**Cross-node staleness note.** Because a backup CronJob may run on a different worker across days (loki/prometheus follow their app pods), a `.prom` file can linger on a node the job has since left and node-exporter keeps exposing it. The freshness alerts collapse the per-node samples with `max()` so the freshest sample wins; check/size alerts fire when *any* node reports a bad value, which is intentional — a recent failure is still a signal until the file is manually cleaned or the job returns to that node. + +## Alerts + +`backup-restic-alerts.yaml` defines seven alerts (all `severity: warning`): + +- `BackupLokiStale` / `BackupGrafanaStale` / `BackupPrometheusStale` — `time() - max(backup__timestamp_seconds) > 28h`. Daily schedule + 4 h grace. `max()` collapses per-node samples so a stale `.prom` on a node the job has left does not fire. +- `BackupLokiCheckFailed` / `BackupGrafanaCheckFailed` / `BackupPrometheusCheckFailed` — `backup__check_status != 0`. +- `ResticRepoOversize` — `restic_repo_size_bytes > 20 GiB`. Baseline expected < 5 GiB; catches retention/prune regressions. + +All alerts carry a `Runbook: docs/monitoring/restic-restore.md` annotation. To iterate on the rule file locally: + +```bash +awk '/^spec:/{f=1;next} f{sub(/^ /,"");print}' \ + apps/monitoring/backup-restic-alerts.yaml > /tmp/backup-restic-rules.yaml +promtool check rules /tmp/backup-restic-rules.yaml +promtool test rules apps/monitoring/backup-restic-alerts.test.yaml +``` + +The legacy `backup-volumes` CronJob and its 100 Gi local-path `backup-storage` PVC were retired in [DEV-489](/DEV/issues/DEV-489) once the restic pipeline was proven end-to-end. With the shared destination PVC gone, the DEV-483 bridge `nodeSelector` pinning Loki to `k3s-worker-2` was also removed — `backup-loki-restic` follows the Loki pod via `podAffinity` regardless of which node the RWO CSI volume lands on. `backup-grafana-restic` still nodeSelects `k3s-worker-2` because its source PV (`grafana-storage`, local-path) is anchored there. `prometheus-backup` uses `podAffinity` on `app=prometheus` (RWO PVC on Hetzner CSI, single-node attach) and follows the Prometheus pod between workers. The `backup-k8s-resources` CronJob and its restic repo were retired in [DEV-576](/DEV/issues/DEV-576) after Velero `cluster-all` took over cluster-manifest coverage. diff --git a/apps/monitoring/backup-grafana-restic-cronjob.yaml b/apps/monitoring/backup-grafana-restic-cronjob.yaml new file mode 100644 index 0000000..1fa69d6 --- /dev/null +++ b/apps/monitoring/backup-grafana-restic-cronjob.yaml @@ -0,0 +1,160 @@ +--- +# Grafana data backup via restic to Hetzner Object Storage (DEV-486, +# DEV-482 Option 4). Step 3 of the Option 4 rollout. +# +# Streams the RWO PVC `grafana-storage` (mounted read-only) into +# `s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/grafana`, a client-side +# encrypted restic repository. +# +# `grafana-storage` is a local-path PV anchored on k3s-worker-2, so +# the grafana pod is already pinned there; a plain nodeSelector on +# the same host is enough (no podAffinity like the loki job needed). +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backup-grafana-restic + namespace: monitoring + labels: + app: backup + type: grafana + backend: restic +spec: + schedule: "15 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + metadata: + labels: + app: backup + type: grafana + backend: restic + spec: + backoffLimit: 2 + activeDeadlineSeconds: 3600 + template: + metadata: + labels: + app: backup + type: grafana + backend: restic + spec: + restartPolicy: OnFailure + nodeSelector: + kubernetes.io/hostname: k3s-worker-2 + containers: + - name: restic + image: harbor.basicstack.de/library/restic:0.19.1 + # Mirrored from docker.io/restic/restic:0.19.1 (DEV-493) — + # deterministic ingress via Harbor. Retag procedure in + # docs/monitoring/restic-restore.md § "Tag-bump procedure". + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: secret-key + - name: RESTIC_PASSWORD + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: restic-password + - name: S3_ENDPOINT + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: endpoint + - name: S3_BUCKET + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: bucket + - name: RESTIC_REPOSITORY + value: "s3:$(S3_ENDPOINT)/$(S3_BUCKET)/restic/grafana" + command: + - /bin/sh + - -c + - | + set -eu + echo "=== backup-grafana-restic started at $(date -u +%FT%TZ) ===" + echo "Repository: ${RESTIC_REPOSITORY}" + + # First-run tolerance: init if the repo isn't there yet. + if restic snapshots >/dev/null 2>&1; then + echo "Repo exists, skipping init." + else + echo "Repo missing, initialising..." + restic init + fi + + echo "--- restic backup /source ---" + restic backup /source \ + --tag grafana \ + --host k3s \ + --exclude '*.tmp' + + echo "--- restic forget/prune ---" + restic forget --tag grafana \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 \ + --prune + + echo "--- restic check --read-data-subset=5% ---" + CHECK_STATUS=0 + restic check --read-data-subset=5% || CHECK_STATUS=$? + echo "restic check exit: ${CHECK_STATUS}" + + echo "--- restic stats (repo size) ---" + REPO_SIZE_BYTES=$(restic stats --json --mode raw-data 2>/dev/null \ + | grep -oE '"total_size":[0-9]+' \ + | head -1 \ + | cut -d: -f2) + REPO_SIZE_BYTES=${REPO_SIZE_BYTES:-0} + echo "restic repo size: ${REPO_SIZE_BYTES} bytes" + + # Textfile-collector metrics; identical wiring to the + # loki sibling. See that file for the atomic-write + # rationale (DEV-494). + { + echo "backup_grafana_success $([ ${CHECK_STATUS} -eq 0 ] && echo 1 || echo 0)" + echo "backup_grafana_timestamp_seconds $(date +%s)" + echo "backup_grafana_check_status ${CHECK_STATUS}" + echo "restic_repo_size_bytes{repo=\"grafana\"} ${REPO_SIZE_BYTES}" + } > /metrics/backup_grafana.prom.tmp + mv /metrics/backup_grafana.prom.tmp /metrics/backup_grafana.prom + + echo "=== backup-grafana-restic finished at $(date -u +%FT%TZ) ===" + exit ${CHECK_STATUS} + volumeMounts: + - name: grafana-data + mountPath: /source + readOnly: true + - name: metrics + mountPath: /metrics + - name: cache + mountPath: /root/.cache/restic + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 1500m + memory: 1Gi + volumes: + - name: grafana-data + persistentVolumeClaim: + claimName: grafana-storage + - name: metrics + hostPath: + # node-exporter's textfile-collector directory + # (DEV-494). See sibling loki cronjob for detail. + path: /var/lib/node_exporter/textfile_collector + type: DirectoryOrCreate + - name: cache + emptyDir: {} diff --git a/apps/monitoring/backup-loki-restic-cronjob.yaml b/apps/monitoring/backup-loki-restic-cronjob.yaml new file mode 100644 index 0000000..0891ffa --- /dev/null +++ b/apps/monitoring/backup-loki-restic-cronjob.yaml @@ -0,0 +1,187 @@ +--- +# Loki data backup via restic to Hetzner Object Storage (DEV-485, +# DEV-482 Option 4). Step 2 of the Option 4 rollout. +# +# Streams the RWO PVC `loki-storage-encrypted` (mounted read-only) +# into `s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/loki`, a client-side +# encrypted restic repository. +# +# podAffinity co-schedules with the Loki pod (app=loki, topology +# kubernetes.io/hostname). RWO permits additional read-only mounts +# on the node that holds the PVC's VolumeAttachment, so this +# survives Loki being rescheduled to a different worker. +apiVersion: batch/v1 +kind: CronJob +metadata: + name: backup-loki-restic + namespace: monitoring + labels: + app: backup + type: loki + backend: restic +spec: + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + metadata: + labels: + app: backup + type: loki + backend: restic + spec: + backoffLimit: 2 + activeDeadlineSeconds: 3600 + template: + metadata: + labels: + app: backup + type: loki + backend: restic + spec: + restartPolicy: OnFailure + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - loki + topologyKey: kubernetes.io/hostname + containers: + - name: restic + image: harbor.basicstack.de/library/restic:0.19.1 + # Mirrored from docker.io/restic/restic:0.19.1 (DEV-493) — + # deterministic ingress via Harbor. Retag procedure in + # docs/monitoring/restic-restore.md § "Tag-bump procedure". + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: secret-key + - name: RESTIC_PASSWORD + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: restic-password + - name: S3_ENDPOINT + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: endpoint + - name: S3_BUCKET + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: bucket + - name: RESTIC_REPOSITORY + value: "s3:$(S3_ENDPOINT)/$(S3_BUCKET)/restic/loki" + command: + - /bin/sh + - -c + - | + set -eu + echo "=== backup-loki-restic started at $(date -u +%FT%TZ) ===" + echo "Repository: ${RESTIC_REPOSITORY}" + + # First-run tolerance: init if the repo isn't there yet. + # `restic cat config` is the tightest existence probe; use + # `snapshots` per plan spec — either exits 0 iff the repo + # is initialised. + if restic snapshots >/dev/null 2>&1; then + echo "Repo exists, skipping init." + else + echo "Repo missing, initialising..." + restic init + fi + + echo "--- restic backup /source ---" + restic backup /source \ + --tag loki \ + --host k3s \ + --exclude '*.tmp' + + echo "--- restic forget/prune ---" + restic forget --tag loki \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 \ + --prune + + echo "--- restic check --read-data-subset=5% ---" + CHECK_STATUS=0 + restic check --read-data-subset=5% || CHECK_STATUS=$? + echo "restic check exit: ${CHECK_STATUS}" + + echo "--- restic stats (repo size) ---" + # `restic stats --json --mode raw-data` prints e.g. + # {"total_size":123,"total_file_count":45,...}. Extract + # total_size without jq (not present in the restic image) + # via grep/cut; fall back to 0 on empty output. + REPO_SIZE_BYTES=$(restic stats --json --mode raw-data 2>/dev/null \ + | grep -oE '"total_size":[0-9]+' \ + | head -1 \ + | cut -d: -f2) + REPO_SIZE_BYTES=${REPO_SIZE_BYTES:-0} + echo "restic repo size: ${REPO_SIZE_BYTES} bytes" + + # Textfile-collector metrics. Written to the shared + # host directory that node-exporter's textfile collector + # scrapes (DEV-494). Atomic write: build the file with a + # `.tmp` extension (ignored by node-exporter) and rename + # into place, so a mid-write read never surfaces a + # truncated sample. + { + echo "backup_loki_success $([ ${CHECK_STATUS} -eq 0 ] && echo 1 || echo 0)" + echo "backup_loki_timestamp_seconds $(date +%s)" + echo "backup_loki_check_status ${CHECK_STATUS}" + echo "restic_repo_size_bytes{repo=\"loki\"} ${REPO_SIZE_BYTES}" + } > /metrics/backup_loki.prom.tmp + mv /metrics/backup_loki.prom.tmp /metrics/backup_loki.prom + + echo "=== backup-loki-restic finished at $(date -u +%FT%TZ) ===" + exit ${CHECK_STATUS} + volumeMounts: + - name: loki-data + mountPath: /source + readOnly: true + - name: metrics + mountPath: /metrics + - name: cache + mountPath: /root/.cache/restic + resources: + # Requests deliberately lowered from the plan doc's + # 200m/256Mi — worker-2 (Loki node) has ~150m free CPU + # and podAffinity forces us onto it. 100m/128Mi mirrors + # the sibling backup CronJobs; limits stay generous so + # restic can burst during pack/check. + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 1500m + memory: 1Gi + volumes: + - name: loki-data + persistentVolumeClaim: + claimName: loki-storage-encrypted + - name: metrics + hostPath: + # node-exporter's textfile-collector directory + # (DEV-494). `DirectoryOrCreate` lets kubelet create + # the dir on the current node if it does not yet + # exist — nodes were pre-created out-of-band, this + # is a safety net. + path: /var/lib/node_exporter/textfile_collector + type: DirectoryOrCreate + - name: cache + emptyDir: {} diff --git a/apps/monitoring/backup-restic-alerts.test.yaml b/apps/monitoring/backup-restic-alerts.test.yaml new file mode 100644 index 0000000..f13a3b6 --- /dev/null +++ b/apps/monitoring/backup-restic-alerts.test.yaml @@ -0,0 +1,216 @@ +--- +# promtool test file for backup-restic-alerts.yaml (DEV-490 DoD). +# +# Extract the rules from the PrometheusRule wrapper first: +# awk '/^spec:/{f=1;next} f{sub(/^ /,"");print}' \ +# apps/monitoring/backup-restic-alerts.yaml > /tmp/rules.yaml +# Then run: +# promtool test rules apps/monitoring/backup-restic-alerts.test.yaml +# (this file uses `rule_files: [/tmp/rules.yaml]` — pass the extracted +# path explicitly). +rule_files: + - /tmp/backup-restic-rules.yaml + +# Evaluation cadence for the test scenarios. The alerts' `for:` uses 5m/15m/30m, +# so we run 60m of series and evaluate at 60m so all alerts have had time to +# settle into the firing state. +evaluation_interval: 1m + +tests: + # --------------------------------------------------------------------------- + # 1. Freshness alerts fire when timestamp is stale (>28h old). + # --------------------------------------------------------------------------- + - interval: 1m + input_series: + # Timestamp value = "current time - 29h" in Unix seconds. During the + # test, promtool's clock starts at the Unix epoch (0). We fix the + # value to a constant far in the past so `time() - value > 28*3600` + # for the entire test run. + # + # Concretely: time() returns the sample timestamp in seconds. Over a + # 60-minute test starting at t=0, time() runs 0 .. 3600. A constant + # value of -104400 (=-29h) keeps `time() - value` >= 29h across the + # whole window, safely above the 28h threshold. + - series: 'backup_loki_timestamp_seconds' + values: '-104400x60' + - series: 'backup_grafana_timestamp_seconds' + values: '-104400x60' + - series: 'backup_prometheus_timestamp_seconds' + values: '-104400x60' + alert_rule_test: + - eval_time: 30m + alertname: BackupLokiStale + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: loki + exp_annotations: + summary: "Loki restic backup is stale (>28h)" + description: | + `backup-loki-restic` has not written a fresh + `backup_loki_timestamp_seconds` sample in more than + 28 hours. Expected daily at 03:00 UTC. + Runbook: docs/monitoring/restic-restore.md + - eval_time: 30m + alertname: BackupGrafanaStale + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: grafana + exp_annotations: + summary: "Grafana restic backup is stale (>28h)" + description: | + `backup-grafana-restic` has not written a fresh + `backup_grafana_timestamp_seconds` sample in more + than 28 hours. Expected daily at 03:15 UTC. + Runbook: docs/monitoring/restic-restore.md + - eval_time: 30m + alertname: BackupPrometheusStale + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: prometheus + exp_annotations: + summary: "Prometheus restic backup is stale (>28h)" + description: | + `prometheus-backup` has not written a fresh + `backup_prometheus_timestamp_seconds` sample in more + than 28 hours. Expected daily at 03:30 UTC. + Runbook: docs/monitoring/restic-restore.md + + # --------------------------------------------------------------------------- + # 2. Freshness alerts stay silent when timestamp is fresh (<28h). + # --------------------------------------------------------------------------- + - interval: 1m + input_series: + # Value = -3600 (=-1h) → time() - value stays around 1-2h, well below + # the 28h threshold across the run. + - series: 'backup_loki_timestamp_seconds' + values: '-3600x60' + - series: 'backup_grafana_timestamp_seconds' + values: '-3600x60' + - series: 'backup_prometheus_timestamp_seconds' + values: '-3600x60' + alert_rule_test: + - eval_time: 30m + alertname: BackupLokiStale + exp_alerts: [] + - eval_time: 30m + alertname: BackupGrafanaStale + exp_alerts: [] + - eval_time: 30m + alertname: BackupPrometheusStale + exp_alerts: [] + + # --------------------------------------------------------------------------- + # 3. Check-status alerts fire when the metric is non-zero. + # --------------------------------------------------------------------------- + - interval: 1m + input_series: + - series: 'backup_loki_check_status' + values: '1x30' + - series: 'backup_grafana_check_status' + values: '2x30' + - series: 'backup_prometheus_check_status' + values: '1x30' + alert_rule_test: + - eval_time: 15m + alertname: BackupLokiCheckFailed + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: loki + exp_annotations: + summary: "restic check failed on loki repo" + description: | + `restic check --read-data-subset=5%` returned + exit code 1 on the loki repository. + Runbook: docs/monitoring/restic-restore.md + - eval_time: 15m + alertname: BackupGrafanaCheckFailed + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: grafana + exp_annotations: + summary: "restic check failed on grafana repo" + description: | + `restic check --read-data-subset=5%` returned + exit code 2 on the grafana repository. + Runbook: docs/monitoring/restic-restore.md + - eval_time: 15m + alertname: BackupPrometheusCheckFailed + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: prometheus + exp_annotations: + summary: "restic check failed on prometheus repo" + description: | + `restic check --read-data-subset=5%` returned + exit code 1 on the prometheus + repository. + Runbook: docs/monitoring/restic-restore.md + + # --------------------------------------------------------------------------- + # 4. Check-status alerts stay silent on 0. + # --------------------------------------------------------------------------- + - interval: 1m + input_series: + - series: 'backup_loki_check_status' + values: '0x30' + - series: 'backup_grafana_check_status' + values: '0x30' + - series: 'backup_prometheus_check_status' + values: '0x30' + alert_rule_test: + - eval_time: 15m + alertname: BackupLokiCheckFailed + exp_alerts: [] + - eval_time: 15m + alertname: BackupGrafanaCheckFailed + exp_alerts: [] + - eval_time: 15m + alertname: BackupPrometheusCheckFailed + exp_alerts: [] + + # --------------------------------------------------------------------------- + # 5. Repo-size alert fires when >20 GiB (21474836480 bytes). + # --------------------------------------------------------------------------- + - interval: 1m + input_series: + # 25 GiB = 26843545600 bytes. + - series: 'restic_repo_size_bytes{repo="loki"}' + values: '26843545600x45' + # 15 GiB = 16106127360 bytes (below threshold). + - series: 'restic_repo_size_bytes{repo="grafana"}' + values: '16106127360x45' + alert_rule_test: + - eval_time: 40m + alertname: ResticRepoOversize + exp_alerts: + - exp_labels: + severity: warning + service: monitoring + component: backup + repo: loki + exp_annotations: + summary: "restic repository loki exceeds 20 GiB" + description: | + Repository loki is + 25GiB, above the 20 GiB + guard. Baseline is <5 GiB per repo. Investigate + retention/prune (see forget flags in the CronJob) + and dedup effectiveness. diff --git a/apps/monitoring/backup-restic-alerts.yaml b/apps/monitoring/backup-restic-alerts.yaml new file mode 100644 index 0000000..e0364ef --- /dev/null +++ b/apps/monitoring/backup-restic-alerts.yaml @@ -0,0 +1,186 @@ +--- +# Prometheus alerting rules for the restic-based monitoring backups +# (DEV-490 / DEV-482 Option 4). Freshness alerts fire when a +# CronJob has not emitted its `backup__timestamp_seconds` +# metric within 28h (schedules are daily; 28h gives one missed +# run + 4h grace before we page). +# +# Check-status alerts fire on the first non-zero exit from +# `restic check --read-data-subset=5%`. +# +# Repo-size alerts fire when a repository exceeds 20 GiB — the +# baseline is expected < 5 GiB per repo. The alert catches +# retention/prune bugs and runaway growth. The metric is emitted +# by the same CronJob step (see backup-*-restic-cronjob.yaml, +# `restic stats --json --mode raw-data`). +# +# The metrics are written to the node's textfile-collector directory +# (`/var/lib/node_exporter/textfile_collector`) and scraped by the +# kube-prometheus-stack node-exporter DaemonSet (DEV-494). +# +# Because a CronJob may run on a different worker across days (loki +# backup follows the loki pod), stale +# `.prom` files can linger on nodes the job has since left. That +# would leave a per-node series with an old timestamp/check-status +# indefinitely. The alerts below aggregate across instances so a +# single fresh sample from the node where the job currently runs is +# enough to keep the freshness alert quiet, and check/size alerts +# fire when *any* node reports a bad value (which is the correct +# behaviour — a recent failure is still a signal). +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: backup-restic-alerts + namespace: monitoring + labels: + app: backup + backend: restic + release: kube-prometheus-stack +spec: + groups: + - name: backup-restic.freshness + interval: 60s + rules: + - alert: BackupLokiStale + # `max()` collapses per-node samples so a stale `.prom` file + # left behind on a node the loki backup no longer runs on + # does not fire this alert; the freshest sample wins. + expr: time() - max(backup_loki_timestamp_seconds) > 28 * 3600 + for: 15m + labels: + severity: warning + service: monitoring + component: backup + repo: loki + annotations: + summary: "Loki restic backup is stale (>28h)" + description: | + `backup-loki-restic` has not written a fresh + `backup_loki_timestamp_seconds` sample in more than + 28 hours. Expected daily at 03:00 UTC. + Runbook: docs/monitoring/restic-restore.md + - alert: BackupGrafanaStale + expr: time() - max(backup_grafana_timestamp_seconds) > 28 * 3600 + for: 15m + labels: + severity: warning + service: monitoring + component: backup + repo: grafana + annotations: + summary: "Grafana restic backup is stale (>28h)" + description: | + `backup-grafana-restic` has not written a fresh + `backup_grafana_timestamp_seconds` sample in more + than 28 hours. Expected daily at 03:15 UTC. + Runbook: docs/monitoring/restic-restore.md + - alert: BackupPrometheusStale + expr: time() - max(backup_prometheus_timestamp_seconds) > 28 * 3600 + for: 15m + labels: + severity: warning + service: monitoring + component: backup + repo: prometheus + annotations: + summary: "Prometheus restic backup is stale (>28h)" + description: | + `prometheus-backup` has not written a fresh + `backup_prometheus_timestamp_seconds` sample in more + than 28 hours. Expected daily at 03:30 UTC. + Runbook: docs/monitoring/restic-restore.md + - alert: BackupForgejoStale + expr: time() - max(backup_forgejo_timestamp_seconds) > 28 * 3600 + for: 15m + labels: + severity: warning + service: monitoring + component: backup + repo: forgejo + annotations: + summary: "Forgejo restic backup is stale (>28h)" + description: | + `forgejo-backup` has not written a fresh + `backup_forgejo_timestamp_seconds` sample in more + than 28 hours. Expected daily at 03:00 UTC. + Runbook: docs/monitoring/restic-restore.md + - name: backup-restic.integrity + interval: 60s + rules: + - alert: BackupLokiCheckFailed + expr: backup_loki_check_status != 0 + for: 5m + labels: + severity: warning + service: monitoring + component: backup + repo: loki + annotations: + summary: "restic check failed on loki repo" + description: | + `restic check --read-data-subset=5%` returned + exit code {{ $value }} on the loki repository. + Runbook: docs/monitoring/restic-restore.md + - alert: BackupGrafanaCheckFailed + expr: backup_grafana_check_status != 0 + for: 5m + labels: + severity: warning + service: monitoring + component: backup + repo: grafana + annotations: + summary: "restic check failed on grafana repo" + description: | + `restic check --read-data-subset=5%` returned + exit code {{ $value }} on the grafana repository. + Runbook: docs/monitoring/restic-restore.md + - alert: BackupPrometheusCheckFailed + expr: backup_prometheus_check_status != 0 + for: 5m + labels: + severity: warning + service: monitoring + component: backup + repo: prometheus + annotations: + summary: "restic check failed on prometheus repo" + description: | + `restic check --read-data-subset=5%` returned + exit code {{ $value }} on the prometheus + repository. + Runbook: docs/monitoring/restic-restore.md + - alert: BackupForgejoCheckFailed + expr: backup_forgejo_check_status != 0 + for: 5m + labels: + severity: warning + service: monitoring + component: backup + repo: forgejo + annotations: + summary: "restic check failed on forgejo repo" + description: | + `restic check --read-data-subset=5%` returned + exit code {{ $value }} on the forgejo + repository. + Runbook: docs/monitoring/restic-restore.md + - name: backup-restic.size + interval: 60s + rules: + - alert: ResticRepoOversize + # 20 GiB = 20 * 1024^3 = 21474836480 bytes. + expr: restic_repo_size_bytes > 21474836480 + for: 30m + labels: + severity: warning + service: monitoring + component: backup + annotations: + summary: "restic repository {{ $labels.repo }} exceeds 20 GiB" + description: | + Repository {{ $labels.repo }} is + {{ $value | humanize1024 }}B, above the 20 GiB + guard. Baseline is <5 GiB per repo. Investigate + retention/prune (see forget flags in the CronJob) + and dedup effectiveness. diff --git a/apps/monitoring/loki-deployment.yaml b/apps/monitoring/loki-deployment.yaml new file mode 100644 index 0000000..a729069 --- /dev/null +++ b/apps/monitoring/loki-deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loki + namespace: monitoring + labels: + app: loki +spec: + replicas: 1 + selector: + matchLabels: + app: loki + # loki-storage-encrypted is RWO on hcloud-volumes-encrypted, so a rolling + # update deadlocks (new pod cannot attach the PVC while the old pod holds + # it). Recreate drops the old pod first so the CSI detaches the volume + # before the new pod tries to attach it. Same pattern as harbor's RWO fix. + strategy: + type: Recreate + template: + metadata: + labels: + app: loki + spec: + containers: + - name: loki + image: grafana/loki:2.9.2 + args: + - -config.file=/etc/loki/loki.yaml + ports: + - containerPort: 3100 + name: http + - containerPort: 9096 + name: grpc + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + volumeMounts: + - name: loki-config + mountPath: /etc/loki + - name: loki-storage + mountPath: /loki + volumes: + - name: loki-config + configMap: + name: loki-config + - name: loki-storage + persistentVolumeClaim: + claimName: loki-storage-encrypted diff --git a/apps/monitoring/prometheus-backup-cronjob.yaml b/apps/monitoring/prometheus-backup-cronjob.yaml new file mode 100644 index 0000000..b4a6c91 --- /dev/null +++ b/apps/monitoring/prometheus-backup-cronjob.yaml @@ -0,0 +1,228 @@ +--- +# Prometheus data backup via restic to Hetzner Object Storage +# (DEV-492, DEV-482 Option 4). Replaces the DEV-465 rclone-sync job so +# every monitoring backup ships client-side-encrypted; Hetzner Object +# Storage has no SSE-S3/SSE-KMS, so the previous plaintext-at-rest +# object layout was the only remaining gap. +# +# Streams the RWO PVC `prometheus-data-encrypted` (mounted read-only) +# into `s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/prometheus`, a client-side +# encrypted restic repository (tag=`prometheus`, host=`k3s`). +# +# Node scheduling matches the previous job: podAffinity co-schedules +# with the Prometheus pod (app=prometheus, topology +# kubernetes.io/hostname). Hetzner CSI RWO permits additional read-only +# mounts on the node that holds the PVC's VolumeAttachment, so this +# survives Prometheus being rescheduled to a different worker. +# +# Prometheus TSDB compaction race +# -------------------------------- +# Prometheus rewrites the on-disk store roughly every 2 h: it creates a +# new block dir, then deletes the source dirs. restic walks the source +# tree once and may catch a file that disappeared mid-walk; restic +# 0.17.3 exits 3 ("at least one source file could not be read") in +# that case, and the snapshot excludes only the missing file. The +# next daily run picks up the successor block, so the race is not a +# data-loss risk — but we must not treat exit 3 as a hard failure, or +# the daily job will alert-flap. +# +# Mitigation: +# - exclude `wal/*` (WAL is replayed from a fresh instance on +# restart; we accept losing the last ~15 s of ingested samples +# rather than snapshotting a moving segment) +# - exclude `chunks_head/*` (in-memory head block; ephemeral, would +# be rebuilt from WAL which we do not keep) +# - exclude Prometheus lock/scratch files (`lock`, `queries.active`, +# `*.tmp`, `lost+found/*`) +# - treat restic exit code 3 as a soft warning (log, continue); +# any other non-zero exit is still fatal +# - restore drill re-runs `promtool tsdb analyze` against every +# block so a corrupted snapshot is caught end-to-end +apiVersion: batch/v1 +kind: CronJob +metadata: + name: prometheus-backup + namespace: monitoring + labels: + app: backup + type: prometheus + backend: restic +spec: + schedule: "30 3 * * *" # daily 03:30, offset from loki/grafana + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + metadata: + labels: + app: backup + type: prometheus + backend: restic + spec: + backoffLimit: 2 + activeDeadlineSeconds: 3600 + template: + metadata: + labels: + app: backup + type: prometheus + backend: restic + spec: + restartPolicy: OnFailure + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app + operator: In + values: + - prometheus + topologyKey: kubernetes.io/hostname + containers: + - name: restic + image: harbor.basicstack.de/library/restic:0.19.1 + env: + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: secret-key + - name: RESTIC_PASSWORD + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: restic-password + - name: S3_ENDPOINT + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: endpoint + - name: S3_BUCKET + valueFrom: + secretKeyRef: + name: monitoring-s3-backup + key: bucket + - name: RESTIC_REPOSITORY + value: "s3:$(S3_ENDPOINT)/$(S3_BUCKET)/restic/prometheus" + command: + - /bin/sh + - -c + - | + set -eu + echo "=== backup-prometheus-restic started at $(date -u +%FT%TZ) ===" + echo "Repository: ${RESTIC_REPOSITORY}" + + # First-run tolerance: init if the repo isn't there yet. + if restic snapshots >/dev/null 2>&1; then + echo "Repo exists, skipping init." + else + echo "Repo missing, initialising..." + restic init + fi + + # See "Prometheus TSDB compaction race" in the file + # header. WAL + chunks_head are excluded on purpose; + # exit code 3 (source file vanished mid-walk during a + # compaction) is accepted and reported, any other + # non-zero exit is fatal. + echo "--- restic backup /source (exclude wal/chunks_head + lock files) ---" + BACKUP_STATUS=0 + restic backup /source \ + --tag prometheus \ + --host k3s \ + --exclude 'wal/*' \ + --exclude 'chunks_head/*' \ + --exclude 'lock' \ + --exclude 'queries.active' \ + --exclude 'lost+found/*' \ + --exclude '*.tmp' || BACKUP_STATUS=$? + echo "restic backup exit: ${BACKUP_STATUS}" + if [ "${BACKUP_STATUS}" -eq 0 ]; then + echo "backup: all files captured cleanly" + elif [ "${BACKUP_STATUS}" -eq 3 ]; then + echo "backup: exit 3 (source files vanished mid-walk) — expected under Prometheus compaction, continuing" + else + echo "backup: FATAL exit ${BACKUP_STATUS} (not compaction-race)" + exit ${BACKUP_STATUS} + fi + + echo "--- restic forget/prune ---" + restic forget --tag prometheus \ + --keep-daily 7 \ + --keep-weekly 4 \ + --keep-monthly 6 \ + --prune + + echo "--- restic check --read-data-subset=5% ---" + CHECK_STATUS=0 + restic check --read-data-subset=5% || CHECK_STATUS=$? + echo "restic check exit: ${CHECK_STATUS}" + + echo "--- restic stats (repo size) ---" + REPO_SIZE_BYTES=$(restic stats --json --mode raw-data 2>/dev/null \ + | grep -oE '"total_size":[0-9]+' \ + | head -1 \ + | cut -d: -f2) + REPO_SIZE_BYTES=${REPO_SIZE_BYTES:-0} + echo "restic repo size: ${REPO_SIZE_BYTES} bytes" + + # Textfile-collector metrics. Same hostPath pattern as + # the loki/grafana siblings (DEV-494) — + # written atomically via `.tmp` + rename so a mid-write + # read never surfaces a truncated sample. + # backup_prometheus_success rolls in both stages: the + # backup step (accepting exit 3) and restic check. + BACKUP_OK=0 + if [ "${BACKUP_STATUS}" -eq 0 ] || [ "${BACKUP_STATUS}" -eq 3 ]; then + BACKUP_OK=1 + fi + SUCCESS=0 + if [ "${BACKUP_OK}" -eq 1 ] && [ "${CHECK_STATUS}" -eq 0 ]; then + SUCCESS=1 + fi + { + echo "backup_prometheus_success ${SUCCESS}" + echo "backup_prometheus_timestamp_seconds $(date +%s)" + echo "backup_prometheus_check_status ${CHECK_STATUS}" + echo "backup_prometheus_backup_status ${BACKUP_STATUS}" + echo "restic_repo_size_bytes{repo=\"prometheus\"} ${REPO_SIZE_BYTES}" + } > /metrics/backup_prometheus.prom.tmp + mv /metrics/backup_prometheus.prom.tmp /metrics/backup_prometheus.prom + + echo "=== backup-prometheus-restic finished at $(date -u +%FT%TZ) ===" + exit ${CHECK_STATUS} + volumeMounts: + - name: prometheus-data + mountPath: /source + readOnly: true + - name: metrics + mountPath: /metrics + - name: cache + mountPath: /root/.cache/restic + resources: + # Prometheus TSDB is ~8-10 GiB; give restic room to + # burst during pack/check but keep steady-state small. + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1500m + memory: 1Gi + volumes: + - name: prometheus-data + persistentVolumeClaim: + claimName: prometheus-data-encrypted + - name: metrics + hostPath: + # node-exporter's textfile-collector directory + # (DEV-494). See sibling loki cronjob for detail. + path: /var/lib/node_exporter/textfile_collector + type: DirectoryOrCreate + - name: cache + emptyDir: {} diff --git a/apps/monitoring/prometheus-backup-sealed.yaml b/apps/monitoring/prometheus-backup-sealed.yaml new file mode 100644 index 0000000..a4b0bb8 --- /dev/null +++ b/apps/monitoring/prometheus-backup-sealed.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: monitoring-s3-backup + namespace: monitoring +spec: + encryptedData: + access-key: AgBf1XxCF3/Uv1vVlJHyTSEMONqWBqPfSOax8jJJHy3NOpgGga3SsxDfAbPXe2rBOYSgGzRZcaT8TcNg8Ng1ipv5Dhu7LzmRb6v6fW0NimrHSU/dtzzJSIR7jQ1sf+Qn4CPS73ReDRvUJ7g4o57ClZIVFBk9oPUDJYdjtg9kAM5Me0ZyqVp4Cnyj2TRXYcQPM+RN1r/PXqK4MhlUsmhsbQCPYwocM0liqv0T8L5xR1g2845oMaMx2nmLs1c1R8nCRugK8IeBKf/CCKLYg8yTqWKS2arrfxm+dkl1mrqLdOgnXrqeeo/63lT1sbd06ub86PJr83MXWW0yh1Uc4r7DX7xK/JIGJ4w+jtQa0vRutYANDcdeVlzjVl+ghoxxw/ZizYVQ/mUFCc+ApJD9u709mI39xof77U5yYBRhnZEExRseL756APubmCc0tPfUSNA1GmMhiYaMnyWu6F5Okreto/03AgI3t7lQm9SPRRM7hcHETG/QC8PrZ0NZEN/A2Nm/ZzwDG4HPDaNxH+IV90f5X4wuh6Mh2lffidOix88rkaizQAeVY5gt8tvfSfyfRc5RoOOov+fvUVCqJHnWaD1A2lKScVeCgLv8/0CJTyR0zM6cg2XN8z0HvU/sahfZrG23fVs3E/IFM5D7//CDp+GfcwQOCBZKPLvJtTRh5nA5EbXQusGEaMOTBiiGG0IJKPejKcPRWS1g+owu+G5R6QQjZgNDoP0cPg== + bucket: AgCYDCrQalif3+XZYhe4Xxyypg2OMGvzdNyBmHzMaP+ou1g2Q0wIjH6S1+YpPu+JgmaATZVISoSZ+OIl0mNOibiFMr0kFjuMhvKRt9Pq05+6axnqnJ17U2GNsZQtY3Ic93qxkkhJKCOQ87mvAJ5AIVBc3pAqfkD5YYTllgnSKwUwyaWK3iX3hxuKLYDvsQ2WTtcc/gsnlCkm1dwPE4E7DFt4wh+jQJRD314Mt7KSpvPBpz7yVVFFX+xRKCfRxjcwshUSUI5OgagfOQvsivkxlL0oSqA9veX2MJpd0SJH0T1Y0na0evkJitxxe+ECwM4FluK/iwW4cdUu+EpQIdd5otY9C5b8e8PJp8ah1k1hYzXdX+j975MeyXWMUn/NgXfyT2CUef6hmmmImialrSYsHT72bz3xpAnZ/oQ/0qcZpNje2oJeokNgkoiC0E5XRnfwEF9jL3XL+aB6gQ3BD9YRptaxLLxOQNj51Oe82vVsOnUz+utzJO1DtqpF+8I0lvHfgcUhFQytUuLscilfBQO/ru8wh47ZI+LRiS3qCZp2Pqi9+r7Y7jJKcbb2awWp6lgrrqgLMvnbVMuvxBi2hUy0C+EkP8tayRJDzz72utx8FY/v0FWoRDl9BAi8kn11+kbbo8a2OXe+20UkmNzunz5H0ijIxhJWzVh/oLuuLFCS0YJtJNjuqUB0VctAFcR4p5Agua8LA9+73HIBsHBYexzmvEZ6Ng== + endpoint: AgCV6YtzikWYMRwHZM1tT8epq6XiSVqhbxMhS7htVjVUfdR4V7/KIcNExxSBul+TU+RCq3S7fppL7CxKbI/IsEdirRMj2PvESXEmzLY/xId7+wJFEdxeXxn59cbV2MfCyysKIwzsVXbmzO66yGaeMKU2oyqJVYu+00L9xS0RbtQRbi0UksMxtw0PhZVMee0aHfu9jynWI3lmPzC/EPRuwxQqBIjYM2v2JCDmYjP0fsBSZSJc+SnAaNmmIf7ypcFwy14qIF7C9tIg02lBx+Sg30oMPjn600AbyF6RmZ+mdclp/Qp+zXOUsbMym0Lu9BjbM7G/z0ExpR5sgE/6+05gJhPF7MuHkrYIMo9LHWWdgzNTnjikSTGEeuFzzRlThPMoUfVi/S7KhG9NtQRnNqEoVCGGW6tt5sygXVE/lWSnmmd//4jXvF9ZJHU+5LrjL7UfVIYNH4y7aYtXpoTgmUe85wWqeMdqr1EiSVueoSwCggGITYoXoKYfERfij8ln/xnX1Kt2xxuyLtpDOczBYZK6LLwTpaXVVe+lO/xBgevWaoEz6si2hyj+glluMQWxPsOnPwUfRi5qqlQxgMZK/9aALYhPLquc16iMG+4FdtoOUfQ0XlLvurHsbCt9lUy3YrsmpDhv42FfamVlcK+gMz7mqYhNhd3fVjxCiUuwvzd01QHcx8ikv48YOnDuRiweRszbPNvlaK2KH6fo7b4l9mmaa4W+MaxZCxG3KSMOafwRmyusEUVNWw== + secret-key: AgBItdfzRgcgJvK5HALDnkbGH1DveAgcmS+Q4f16VwX1KY8TzGWpjoaOdsLCwzTWOFME5xjrRyOwwAcdtP65ccWlEZvvoS4kpqv49G1ebblD1wmApnpa8vD63Rd028vUYFF3CnwntX2USxF6t4rD68sZqqzMDyuG9Av93HAKVMNdx9V4AJ45nvfF2WR7ohk37NhQifOdKuelgRV6ahl3/fm/XPRGzf+u7OER8jll9n/ywE0mSkoKBXocbkQ2Xrn+utzplh9BoR9KSPTdnmI8ZXUkDTR9p+7PN4r/HheEdTUG0aaefdEDCbdDlv1EW84l5KzhY4v9g0DW8GQQdyHWi3sMm0hSGFg9NV/Vs1HM9FZZCyjS+mzR4CbuHgkY9mvj+Svn7oS+Fy5d24s1ekmPheakBffXKy2Q2CLMRGNymjUIBQsDwoGC8o4IF+yPyDcw/OvXlr0lruxfRM6IN0oxgRmLAQiqwRzsZb3RPgILEZI9VXKxzCBEuCO7fE2pC05K/mujhUutWmJ5tQ0NFzP1mhNK59cAIVMl7y7Llq5/GZ5ax+voP0c3VandXJeaSRr6O40vyzfCMgpgEgRs9t2WfxCmVYEN2NWpFv07JNICWzJX40PLT9n/euRXzSmJld6UfjzRteaTfmo+OxFczJ6Zl8XdC+6x6wO0Nk0hVYo27s2LH4o0MsM84x9Z3StCCGl77/NnyavpxfDkj3qJQ4BgPtCw26tHTt9DIWYfgeAVqvoRZVlg8YxM3N1k + restic-password: AgBuoBFK5Y2Vlf1Vj6Q7y5OwWV2iOLMATe+Kjca8uwHgiYnpka+619ab52P1P+tkxbGyGdnwF+3q0KzvJJaNfuoduwlPuRjiYmaLLoCuQahZmTLNbzwEFst1cbm4oG6vCTBGsK9IMngT6VMfmvz029X+jsGaJHaECHlJjWnMteKN2pwxbzt4/3Wx6wP3//qAk1ah4QLRZUEWvn6O7djTFWJtzM4MRfZPI6EmM9emlQRN9iHwWSUmgBZ5zvaAylRHHUHK1lkNgWJJr7+tC4D8DbZ+c4+FO6XefXDheG6jO2Ial09KTISC0+v86xKFq/aSko5TKczpdXEdYNfyf/aPJTRd4LTcTySUdIpflmjIUnJBdn2Yn4Jox8LKc6ecUJ+IxMXhWlToFNtSFJwx744uv9WsrQ7p/lH7VTZOT+R+0wKTe9vn07h6B78xX6JJWKHMepb9aJB0gB1fn9cx1KHzm4/Y30SFwuOpYpVx19lyl1BRfSry4kmPuDKouSO6D415NJ/lMNYqTvafRho5SzlFfle/n7wAK7OIRdNbgsabO2mfHjHpw6w8Bvo9K7+3QJiwpeesY8OKAubcpxy1RBDw3fp2JQCNNqIer41vUtprsRU+tBnzDboImhOTXpP71AgTa+L68k/iGVJB0YqjFDxjcJB1f1hKGvH+yCV0GPFSeUFOZ/gmxV+Q0Fmoq/ezLrqpjoTbjp+ZbAi6qbVrMN/3t3lheKf47dU5HEOQMyZkXqDrkyuc7kXhmzN/V/ISmW0= + template: + metadata: + creationTimestamp: null + name: monitoring-s3-backup + namespace: monitoring + type: Opaque diff --git a/apps/monitoring/prometheus-deployment.yaml b/apps/monitoring/prometheus-deployment.yaml new file mode 100644 index 0000000..1010a3a --- /dev/null +++ b/apps/monitoring/prometheus-deployment.yaml @@ -0,0 +1,110 @@ +--- +# Prometheus data volume — RWO on hcloud-volumes-encrypted (fsn1). Because the +# volume is RWO, the Deployment below must use Recreate; a rolling update would +# spin up a surge pod that cannot mount the volume while the old pod holds it +# and the rollout would stall at Init:0/1. See DEV-583 / mirrors DEV-538. +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: prometheus-data-encrypted + namespace: monitoring +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 50Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: prometheus + namespace: monitoring + labels: + app: prometheus +spec: + type: ClusterIP + ports: + - port: 9090 + targetPort: 9090 + name: web + selector: + app: prometheus +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: prometheus + namespace: monitoring + labels: + app: prometheus +spec: + replicas: 1 + # prometheus-data-encrypted is RWO on hcloud-volumes-encrypted, so a rolling + # update deadlocks (new pod cannot attach the PVC while the old pod holds + # it). Recreate drops the old pod first so the CSI detaches the volume + # before the new pod tries to attach it. Same pattern as loki/opencloud. + strategy: + type: Recreate + selector: + matchLabels: + app: prometheus + template: + metadata: + labels: + app: prometheus + annotations: + # Exclude the prometheus TSDB volume from Velero fs-backup — TSDB is + # backed up separately by prometheus-backup-cronjob and streaming it + # through Kopia would double-copy tens of GiB on every backup. + backup.velero.io/backup-volumes-excludes: prometheus-storage + spec: + serviceAccountName: prometheus + initContainers: + - name: fix-permissions + image: busybox:latest + command: + - sh + - -c + - chown -R 65534:65534 /prometheus && chmod -R 755 /prometheus + volumeMounts: + - name: prometheus-storage + mountPath: /prometheus + containers: + - name: prometheus + image: prom/prometheus:v2.47.0 + args: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--storage.tsdb.retention.time=30d' + - '--web.console.libraries=/usr/share/prometheus/console_libraries' + - '--web.console.templates=/usr/share/prometheus/consoles' + - '--web.enable-lifecycle' + ports: + - containerPort: 9090 + name: web + volumeMounts: + - name: prometheus-config + mountPath: /etc/prometheus + - name: prometheus-rules + mountPath: /etc/prometheus/rules + - name: prometheus-storage + mountPath: /prometheus + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: 1000m + memory: 2Gi + volumes: + - name: prometheus-config + configMap: + name: prometheus-config + - name: prometheus-rules + configMap: + name: prometheus-rules + - name: prometheus-storage + persistentVolumeClaim: + claimName: prometheus-data-encrypted diff --git a/apps/observability/patches/README.md b/apps/observability/patches/README.md new file mode 100644 index 0000000..89a3c20 --- /dev/null +++ b/apps/observability/patches/README.md @@ -0,0 +1,38 @@ +# observability patches + +Strategic-merge patches applied on top of Helm-managed observability +resources. Each file is idempotent (re-applying is a no-op) and is +reasserted by hand rather than by a controller, so re-run after any +`helm upgrade` of the affected release. + +## `node-exporter-textfile-collector.yaml` (DEV-494) + +Enables the node-exporter textfile collector on the +`kube-prometheus-stack-prometheus-node-exporter` DaemonSet by: + +1. adding `--collector.textfile.directory=/host/textfile_collector` + to the container args, and +2. mounting the host directory `/var/lib/node_exporter/textfile_collector` + read-only at `/host/textfile_collector` (`hostPath` type + `DirectoryOrCreate`, kubelet creates it on nodes where the + directory does not exist yet). + +The four monitoring backup CronJobs in `apps/monitoring/` write their +textfile-collector `.prom` files into that same host directory, so the +metrics surface in Prometheus via node-exporter's normal scrape. + +Apply / re-apply: + +```bash +kubectl -n observability patch daemonset \ + kube-prometheus-stack-prometheus-node-exporter \ + --type=strategic \ + --patch-file=apps/observability/patches/node-exporter-textfile-collector.yaml +kubectl -n observability rollout status daemonset \ + kube-prometheus-stack-prometheus-node-exporter +``` + +The kube-prometheus-stack chart is not currently tracked in ArgoCD; +if it moves under GitOps, fold these values into the chart values as +`prometheus-node-exporter.extraArgs` + `.extraHostVolumeMounts` +instead of maintaining this patch. diff --git a/apps/observability/patches/node-exporter-textfile-collector.yaml b/apps/observability/patches/node-exporter-textfile-collector.yaml new file mode 100644 index 0000000..1ec8b69 --- /dev/null +++ b/apps/observability/patches/node-exporter-textfile-collector.yaml @@ -0,0 +1,70 @@ +--- +# Strategic-merge patch enabling node-exporter's textfile collector on +# the kube-prometheus-stack node-exporter DaemonSet (DEV-494). +# +# The chart is Helm-managed (release `kube-prometheus-stack` in +# namespace `observability`, chart kube-prometheus-stack-86.2.2 / +# prometheus-node-exporter-4.55.0) and is NOT currently tracked in +# ArgoCD, so a direct DaemonSet patch is the pragmatic wiring path. +# The change: +# +# 1. adds the `--collector.textfile.directory=/host/textfile_collector` +# arg to the node-exporter container, and +# 2. mounts the host directory `/var/lib/node_exporter/textfile_collector` +# read-only at `/host/textfile_collector` (type DirectoryOrCreate so +# kubelet creates the dir on nodes where it does not yet exist). +# +# Apply with: +# kubectl -n observability patch daemonset \ +# kube-prometheus-stack-prometheus-node-exporter \ +# --type=strategic \ +# --patch-file=apps/observability/patches/node-exporter-textfile-collector.yaml +# +# If the Helm release is ever `helm upgrade`d without folding these +# values into the chart values, this patch will be reverted — re-apply +# it after the upgrade (or move it into a repo-owned values file). +spec: + template: + spec: + containers: + - name: node-exporter + args: + - --path.procfs=/host/proc + - --path.sysfs=/host/sys + - --path.rootfs=/host/root + - --path.udev.data=/host/root/run/udev/data + - --web.listen-address=[$(HOST_IP)]:9100 + - --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|run/containerd/.+|var/lib/docker/.+|var/lib/kubelet/.+)($|/) + - --collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs|erofs)$ + - --collector.textfile.directory=/host/textfile_collector + volumeMounts: + - mountPath: /host/proc + name: proc + readOnly: true + - mountPath: /host/sys + name: sys + readOnly: true + - mountPath: /host/root + mountPropagation: HostToContainer + name: root + readOnly: true + - mountPath: /host/textfile_collector + name: textfile-collector + readOnly: true + volumes: + - hostPath: + path: /proc + type: "" + name: proc + - hostPath: + path: /sys + type: "" + name: sys + - hostPath: + path: / + type: "" + name: root + - hostPath: + path: /var/lib/node_exporter/textfile_collector + type: DirectoryOrCreate + name: textfile-collector diff --git a/apps/opencloud/OPENLDAP_DEPLOYMENT.md b/apps/opencloud/OPENLDAP_DEPLOYMENT.md deleted file mode 100644 index 3365068..0000000 --- a/apps/opencloud/OPENLDAP_DEPLOYMENT.md +++ /dev/null @@ -1,257 +0,0 @@ -# OpenCloud with External OpenLDAP Deployment Guide - -## Architecture Overview - -This deployment uses: -- **External OpenLDAP** for user/group storage -- **Pocket ID** (auth.basicstack.de) for authentication via OIDC -- **OpenCloud** as the file storage platform - -### Authentication Flow - -1. User accesses `https://opencloud.basicstack.de` -2. OpenCloud redirects to Pocket ID for authentication -3. User logs in at `https://auth.basicstack.de` -4. Pocket ID returns OIDC token to OpenCloud -5. OpenCloud auto-provisions user in external OpenLDAP -6. User is granted access based on OIDC group claims - -## Deployment Steps - -### 1. Create OpenLDAP Admin Secret - -First, generate strong passwords and create the sealed secret: - -```bash -# Generate passwords -ADMIN_PASSWORD=$(openssl rand -base64 32) -CONFIG_PASSWORD=$(openssl rand -base64 32) - -# Save them securely (e.g., password manager) -echo "Admin Password: $ADMIN_PASSWORD" -echo "Config Password: $CONFIG_PASSWORD" - -# Create sealed secret -kubectl create secret generic openldap-admin-secret \ - --namespace opencloud \ - --from-literal=admin-password="$ADMIN_PASSWORD" \ - --from-literal=config-password="$CONFIG_PASSWORD" \ - --dry-run=client -o yaml | \ - kubeseal --format yaml > openldap-admin-sealed.yaml - -# Apply sealed secret -kubectl apply -f openldap-admin-sealed.yaml -``` - -### 2. Deploy OpenLDAP - -```bash -# Deploy OpenLDAP -kubectl apply -f openldap-deployment.yaml - -# Wait for OpenLDAP to be ready -kubectl wait --for=condition=ready pod -l app=openldap -n opencloud --timeout=120s - -# Check OpenLDAP pod status -kubectl get pods -n opencloud -l app=openldap -kubectl logs -n opencloud -l app=openldap -f -``` - -### 3. Initialize LDAP Directory Structure - -Once OpenLDAP is running, initialize the directory with required OUs: - -```bash -# Get the OpenLDAP pod name -LDAP_POD=$(kubectl get pods -n opencloud -l app=openldap -o jsonpath='{.items[0].metadata.name}') - -# Copy the LDIF file to the pod -kubectl cp ldap-init-structure.ldif opencloud/$LDAP_POD:/tmp/init-structure.ldif - -# Apply the LDIF (replace with your actual password) -kubectl exec -n opencloud $LDAP_POD -- \ - ldapadd -x -D "cn=admin,dc=basicstack,dc=de" -w "" -f /tmp/init-structure.ldif - -# Verify structure was created -kubectl exec -n opencloud $LDAP_POD -- \ - ldapsearch -x -D "cn=admin,dc=basicstack,dc=de" -w "" -b "dc=basicstack,dc=de" -LLL -``` - -### 4. Deploy OpenCloud - -**Important:** Make sure the old OpenCloud PVC is deleted first to ensure fresh initialization with new LDAP configuration. - -```bash -# Scale down existing OpenCloud deployment (if running) -kubectl scale deployment -n opencloud opencloud --replicas=0 - -# Delete old PVC -kubectl delete pvc -n opencloud opencloud-data - -# Apply updated deployment -kubectl apply -f opencloud-deployment.yaml -kubectl apply -f opencloud-configmap.yaml - -# Scale up -kubectl scale deployment -n opencloud opencloud --replicas=1 - -# Watch initialization -kubectl logs -n opencloud -l app=opencloud -f -``` - -### 5. Verify Configuration - -```bash -# Check all pods are running -kubectl get pods -n opencloud - -# Verify OpenCloud can connect to LDAP -kubectl exec -n opencloud deployment/opencloud -- \ - ldapsearch -x -H ldap://openldap.opencloud.svc.cluster.local:389 \ - -D "cn=admin,dc=basicstack,dc=de" -w "" \ - -b "dc=basicstack,dc=de" -LLL - -# Check OpenCloud logs for LDAP connection -kubectl logs -n opencloud -l app=opencloud | grep -i ldap -``` - -### 6. Test OIDC Authentication - -1. Open browser to `https://opencloud.basicstack.de` -2. Should redirect to `https://auth.basicstack.de` -3. Login with Pocket ID credentials -4. Should redirect back to OpenCloud -5. User auto-provisioned in OpenLDAP - -## LDAP Directory Structure - -``` -dc=basicstack,dc=de -├── cn=admin (admin user) -├── ou=users -│ └── (auto-provisioned users from OIDC) -└── ou=groups - ├── cn=opencloudUsers (default users group) - └── cn=opencloudAdmins (administrators group) -``` - -## Configuration Details - -### OpenLDAP Connection Details - -- **Service:** `openldap.opencloud.svc.cluster.local` -- **Port:** 389 (LDAP), 636 (LDAPS - disabled for internal use) -- **Base DN:** `dc=basicstack,dc=de` -- **Admin DN:** `cn=admin,dc=basicstack,dc=de` -- **User Base:** `ou=users,dc=basicstack,dc=de` -- **Group Base:** `ou=groups,dc=basicstack,dc=de` - -### OpenCloud Services Excluded - -- **search** - Broken in v7.2.0 -- **idp** - Using external Pocket ID for authentication -- **idm** - Using external OpenLDAP for user storage - -### OIDC Configuration - -- **Issuer:** `https://auth.basicstack.de` -- **Client ID:** (from `opencloud-oidc-secret`) -- **Scopes:** `openid profile email groups offline_access` -- **Auto-provisioning:** Enabled -- **User claim:** `email` -- **Role claim:** `groups` - -### Role Mapping - -OIDC groups → OpenCloud roles: -- `opencloudAdmin` → admin -- `opencloudSpaceAdmin` → spaceadmin -- `opencloudUser` → user -- `opencloudGuest` → guest - -## Troubleshooting - -### OpenLDAP not starting - -```bash -# Check logs -kubectl logs -n opencloud -l app=openldap - -# Check PVCs -kubectl get pvc -n opencloud - -# Check events -kubectl get events -n opencloud --sort-by='.lastTimestamp' | tail -20 -``` - -### OpenCloud can't connect to LDAP - -```bash -# Test LDAP connectivity from OpenCloud pod -kubectl exec -n opencloud deployment/opencloud -- \ - nc -zv openldap.opencloud.svc.cluster.local 389 - -# Check LDAP service -kubectl get svc -n opencloud openldap - -# Check LDAP endpoints -kubectl get endpoints -n opencloud openldap -``` - -### Users not auto-provisioning - -```bash -# Check OpenCloud proxy logs -kubectl logs -n opencloud -l app=opencloud | grep -i provision - -# Check OIDC flow -kubectl logs -n opencloud -l app=opencloud | grep -i oidc - -# Verify LDAP directory structure -kubectl exec -n opencloud deployment/openldap -- \ - ldapsearch -x -D "cn=admin,dc=basicstack,dc=de" -w "" \ - -b "ou=users,dc=basicstack,dc=de" -LLL -``` - -### View auto-provisioned users - -```bash -# List all users in LDAP -kubectl exec -n opencloud deployment/openldap -- \ - ldapsearch -x -D "cn=admin,dc=basicstack,dc=de" -w "" \ - -b "ou=users,dc=basicstack,dc=de" -LLL "(objectClass=inetOrgPerson)" -``` - -## Backup and Maintenance - -### Backup LDAP Data - -```bash -# Backup entire LDAP directory -kubectl exec -n opencloud deployment/openldap -- \ - slapcat -l /tmp/backup.ldif - -kubectl cp opencloud/$LDAP_POD:/tmp/backup.ldif ./ldap-backup-$(date +%Y%m%d).ldif -``` - -### Monitor LDAP Performance - -```bash -# Check LDAP stats -kubectl exec -n opencloud deployment/openldap -- \ - ldapsearch -x -H ldapi:/// -Y EXTERNAL -b "cn=Monitor" -LLL -``` - -## Files - -- `openldap-deployment.yaml` - OpenLDAP Kubernetes deployment -- `openldap-admin-sealed.yaml` - Admin credentials (sealed secret) -- `ldap-init-structure.ldif` - Initial directory structure -- `opencloud-deployment.yaml` - OpenCloud deployment (updated for external LDAP) -- `opencloud-configmap.yaml` - OpenCloud configuration (updated for external LDAP) - -## References - -- OpenLDAP: https://www.openldap.org/ -- OpenCloud LDAP Configuration: https://docs.opencloud.eu/ -- Pocket ID: https://github.com/stonith404/pocket-id diff --git a/apps/opencloud/backup-cronjob.yaml b/apps/opencloud/backup-cronjob.yaml index 273ab1c..d11b2f8 100644 --- a/apps/opencloud/backup-cronjob.yaml +++ b/apps/opencloud/backup-cronjob.yaml @@ -90,8 +90,8 @@ spec: readOnly: true resources: requests: - memory: "256Mi" - cpu: "100m" + memory: "16Mi" + cpu: "20m" limits: memory: "512Mi" cpu: "500m" diff --git a/apps/opencloud/init-job.yaml b/apps/opencloud/init-job.yaml deleted file mode 100644 index da62a6d..0000000 --- a/apps/opencloud/init-job.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: opencloud-init-generator - namespace: opencloud -spec: - ttlSecondsAfterFinished: 300 - template: - spec: - restartPolicy: Never - containers: - - name: init - image: quay.io/opencloudeu/opencloud:7.2.0 - command: ["/bin/sh"] - args: - - -c - - | - echo "=== Running opencloud init ===" - OPENCLOUD_URL=https://opencloud.basicstack.de \ - /usr/bin/opencloud init --insecure true - - echo "" - echo "=== Generated opencloud.yaml ===" - if [ -f /etc/opencloud/opencloud.yaml ]; then - cat /etc/opencloud/opencloud.yaml - else - echo "ERROR: opencloud.yaml not found at /etc/opencloud/" - ls -la /etc/opencloud/ || echo "Directory doesn't exist" - fi diff --git a/apps/opencloud/ldap-init-structure.ldif b/apps/opencloud/ldap-init-structure.ldif deleted file mode 100644 index fcd05b7..0000000 --- a/apps/opencloud/ldap-init-structure.ldif +++ /dev/null @@ -1,37 +0,0 @@ -# OpenLDAP Directory Structure Initialization -# This LDIF creates the organizational units needed by OpenCloud -# -# Apply with: -# kubectl exec -n opencloud deployment/openldap -- \ -# ldapadd -x -D "cn=admin,dc=basicstack,dc=de" -w -f /tmp/init-structure.ldif -# -# Or copy to pod and apply: -# kubectl cp ldap-init-structure.ldif opencloud/openldap-:/tmp/init-structure.ldif -# kubectl exec -n opencloud openldap- -- \ -# ldapadd -x -D "cn=admin,dc=basicstack,dc=de" -w -f /tmp/init-structure.ldif - -# Create Users Organizational Unit -dn: ou=users,dc=basicstack,dc=de -objectClass: organizationalUnit -ou: users -description: OpenCloud Users - -# Create Groups Organizational Unit -dn: ou=groups,dc=basicstack,dc=de -objectClass: organizationalUnit -ou: groups -description: OpenCloud Groups - -# Example: Create default user group -dn: cn=opencloudUsers,ou=groups,dc=basicstack,dc=de -objectClass: groupOfNames -cn: opencloudUsers -description: Default OpenCloud Users Group -member: cn=admin,dc=basicstack,dc=de - -# Example: Create admin group -dn: cn=opencloudAdmins,ou=groups,dc=basicstack,dc=de -objectClass: groupOfNames -cn: opencloudAdmins -description: OpenCloud Administrators -member: cn=admin,dc=basicstack,dc=de diff --git a/apps/opencloud/opencloud-deployment.yaml b/apps/opencloud/opencloud-deployment.yaml index 54baa9b..d705ab2 100644 --- a/apps/opencloud/opencloud-deployment.yaml +++ b/apps/opencloud/opencloud-deployment.yaml @@ -42,6 +42,8 @@ metadata: namespace: opencloud spec: replicas: 1 + strategy: + type: Recreate selector: matchLabels: app: opencloud @@ -49,6 +51,12 @@ spec: metadata: labels: app: opencloud + annotations: + # Skip Kopia fs-backup of the 100 GiB opencloud object-store PVC — this + # blob store is out of scope for the DEV-573 data schedules; the + # exclusion is a safety net so an accidental defaultVolumesToFsBackup + # run never streams it into the Kopia repo. + backup.velero.io/backup-volumes-excludes: opencloud-data spec: securityContext: fsGroup: 1000 @@ -56,7 +64,7 @@ spec: runAsNonRoot: true initContainers: - name: init-dirs - image: opencloudeu/opencloud-rolling:7.2.0 + image: opencloudeu/opencloud-rolling:7.4.0 command: - sh - -c @@ -71,7 +79,7 @@ spec: runAsNonRoot: true containers: - name: opencloud - image: opencloudeu/opencloud-rolling:7.2.0 + image: opencloudeu/opencloud-rolling:7.4.0 command: - /bin/sh - -c @@ -290,13 +298,6 @@ spec: name: opencloud-config-secrets key: ldap-bind-password - # External OpenLDAP admin password (for user/group storage) - - name: OPENLDAP_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: openldap-admin-secret - key: admin-password - # IDM service user passwords - name: OC_IDM_ADMIN_PASSWORD valueFrom: @@ -453,7 +454,7 @@ spec: resources: requests: memory: "512Mi" - cpu: "250m" + cpu: "50m" limits: memory: "2Gi" cpu: "1000m" diff --git a/apps/opencloud/opencloud-ldap-schema-job.yaml b/apps/opencloud/opencloud-ldap-schema-job.yaml deleted file mode 100644 index 156d5cd..0000000 --- a/apps/opencloud/opencloud-ldap-schema-job.yaml +++ /dev/null @@ -1,68 +0,0 @@ ---- -# One-shot Job to load the OpenCloud LDAP schema into the running OpenLDAP instance. -# Connect via network LDAP as cn=admin,cn=config (rootdn of the config database). -# Re-run by deleting and recreating the Job; idempotent (exits 0 if schema already present). -apiVersion: batch/v1 -kind: Job -metadata: - name: opencloud-ldap-schema-loader - namespace: opencloud -spec: - ttlSecondsAfterFinished: 86400 - backoffLimit: 10 - template: - spec: - restartPolicy: OnFailure - containers: - - name: schema-loader - image: osixia/openldap:1.5.0 - command: - - /bin/bash - - -c - - | - set -e - - echo "Waiting for OpenLDAP to be ready..." - until ldapsearch -x -H ldap://openldap:389 \ - -D "cn=admin,dc=basicstack,dc=de" \ - -w "$LDAP_ADMIN_PASSWORD" \ - -b "dc=basicstack,dc=de" \ - -s base "(objectClass=*)" dn 2>&1 | grep -q "result: 0"; do - echo "Not ready yet, retrying in 5s..." - sleep 5 - done - echo "OpenLDAP is ready" - - if ldapsearch -x -H ldap://openldap:389 \ - -D "cn=admin,cn=config" \ - -w "$LDAP_CONFIG_PASSWORD" \ - -b "cn=schema,cn=config" \ - "(cn={*}opencloud)" dn 2>/dev/null | grep -qi "opencloud"; then - echo "OpenCloud schema already present, nothing to do" - exit 0 - fi - - echo "Loading OpenCloud LDAP schema..." - ldapadd -x -H ldap://openldap:389 \ - -D "cn=admin,cn=config" \ - -w "$LDAP_CONFIG_PASSWORD" \ - -f /schemas/10_opencloud_schema.ldif - echo "OpenCloud schema loaded successfully" - env: - - name: LDAP_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: openldap-admin-secret - key: admin-password - - name: LDAP_CONFIG_PASSWORD - valueFrom: - secretKeyRef: - name: openldap-admin-secret - key: config-password - volumeMounts: - - name: schemas - mountPath: /schemas - volumes: - - name: schemas - configMap: - name: opencloud-ldap-schema diff --git a/apps/opencloud/opencloud-ldap-schema.yaml b/apps/opencloud/opencloud-ldap-schema.yaml deleted file mode 100644 index aeae6c7..0000000 --- a/apps/opencloud/opencloud-ldap-schema.yaml +++ /dev/null @@ -1,58 +0,0 @@ ---- -# OpenCloud LDAP schema ConfigMap -# Defines openCloudUser objectClass and related attributes (OIDs under 1.3.6.1.4.1.63016) -# Mounted into OpenLDAP pod and loaded via lifecycle postStart hook -apiVersion: v1 -kind: ConfigMap -metadata: - name: opencloud-ldap-schema - namespace: opencloud -data: - 10_opencloud_schema.ldif: | - dn: cn=opencloud,cn=schema,cn=config - objectClass: olcSchemaConfig - cn: opencloud - olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.1 - NAME 'openCloudUUID' - DESC 'A non-reassignable and persistent account ID' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} - SINGLE-VALUE ) - olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.2 - NAME 'openCloudExternalIdentity' - DESC 'Represents the objectIdentity resource type of the Graph API' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) - olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.3 - NAME 'openCloudUserEnabled' - DESC 'Indicates if the user account is enabled' - EQUALITY booleanMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 - SINGLE-VALUE ) - olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.4 - NAME 'openCloudUserType' - DESC 'Specifies the user type (Member or Guest)' - EQUALITY caseIgnoreMatch - SUBSTR caseIgnoreSubstringsMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 - SINGLE-VALUE ) - olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.5 - NAME 'openCloudLastSignInTimestamp' - DESC 'Timestamp of the most recent authentication event' - EQUALITY generalizedTimeMatch - ORDERING generalizedTimeOrderingMatch - SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 - SINGLE-VALUE ) - olcObjectClasses: ( 1.3.6.1.4.1.63016.1.2.1 - NAME 'openCloudObject' - DESC 'Base auxiliary class for OpenCloud objects' - AUXILIARY - MAY ( openCloudUUID ) ) - olcObjectClasses: ( 1.3.6.1.4.1.63016.1.2.2 - NAME 'openCloudUser' - DESC 'Auxiliary class for OpenCloud user accounts' - AUXILIARY - SUP openCloudObject - MAY ( openCloudExternalIdentity $ openCloudUserEnabled $ openCloudUserType $ openCloudLastSignInTimestamp ) ) diff --git a/apps/opencloud/openldap-admin-sealed.yaml b/apps/opencloud/openldap-admin-sealed.yaml deleted file mode 100644 index 796f408..0000000 --- a/apps/opencloud/openldap-admin-sealed.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: openldap-admin-secret - namespace: opencloud -spec: - encryptedData: - admin-password: AgCfe8Y89Uy1gGJtkD/8QUn0KJMGKHvVc53f8N0tJk0KECT5Bxas9zNxL6UF+3Uxz/IgP4F3ZMgQ0AEy5GpnOHHZkWxjXjs64WGiUauA59MCxMQT6kwKtLkTX7M632S0HKwbS+vJi2S/r6JmMjSfCQJrsf2XKrfeyeg3+MqxwDSjIYdfyUTvNJ190QA0guqEpJltch8+Mr7Va0oxbwO64gDSXyjIipOCEHiGlMEiYlFXgEI/xUBEsvWN5jwgjmCkNTvx0id5+5ZhZHtWbLVBTP0caAqPhgsxjI0OhNPb8ZSzLx2uPgBK4m2jw6Kw4TvL+mJq1tvGjxnxVnDil4GhB4sjm0PKsL1EOeXzJphQWHvk6wDgrf3KW9uQPBODfVH1xUYCkrFWa/YzYM+rYJtnKdUVfqV6U//Vd64BXjz0Opx8twd9ljwlMBnW+8RRy8940m1bGEaKWeZNdXU+gVGEd+E4dYdqycz46UvktQmyFQgNjesGNbpwc+fVw4E2uDCLM8Lo35PnIcGeaMRQeurQuSFbKntFazB6nggkweesiFcPLSoHWUsvrdyK7h9lkLOg0KG5SoUs4aUkd2L4UpLHGMxV2PioxMPI6epe3AFIkcuMO/ukJuA7Cg58P9ZUZiED2dhkF9hOoUibaAKndwdPtQzBQUofj0LMVqHcSKjH3jC1eOAbKzvTHELfdWql5fldE5IEFP/HkfguGAqvV8XGm4QPb4D1Tj2raMHiiG39rq4mHgcdF++9IGjxdTYnaw== - config-password: AgBtWsvBgzOq6mHKZ7HLd/SqEVGFozSkXKWyqLUVKFNNnjwVwvZU/vUFKzKxC7xqoNjccqrQjcxbAPAA8USxZyUrW+Qws/v7J4w2bvtg3nklZfG8FGZZvaLFkUJKV0ZWa+kg3CFoFk5hHH3IpesfZTOCJPTiCey61os0m8DS1g9fre1eKVZz5RFk3d79Pv+O8P4ao9RF+dXPYuBMLHxg+b77p/LzeV0sCIicdLKNrQaPGj8OP9JyGpD8XLX3enWFtFNtX48gLnVGrzNLWo/Ic2LQZoqM7XUB2d/zlB6CN6KrnkZEoR3L5GPU7fVXAga6L1JqTiPD8SG7BnWlxdKZEeoko94NciBQcIG1igE4+HTMBx3kbES+eh+kV2ImLzKTYuUuoN7TUJnOXP10x2zf1OhdKumqFqNFVPPJW8zDtzIflkzwFN79cY+WP5XOL/XUnLIISIGGpW4tTyfsLL3BnoWtkfuSjeH6JEsq8xOcVSyg0/6hCmz9ZztkmqMjTEkL0++agaXxPQgyvsxX8r/8TCwVt02RGRbPUQqH9r6/xdIX4SQEzoFhbINqPgCVuY7U/cNqAOxwP7FmG6Dcm8wAEtFTEFqU3T5eo0iuQqpfN3iXsFahf7e28Jh2N5JS344RiqA0HAmu4QT67maXLQrvkbL6zYY5pROsm2y2xgZP432AZ98hw3BooDelkLZZ9A5HNgBg8I/kSBgX2xLZR+Nc5L7KH240zR5/0Hc0jh9fjzdibISQ76y4gTEp1Y2KSg== - template: - metadata: - creationTimestamp: null - name: openldap-admin-secret - namespace: opencloud diff --git a/apps/opencloud/openldap-deployment.yaml b/apps/opencloud/openldap-deployment.yaml deleted file mode 100644 index 8885843..0000000 --- a/apps/opencloud/openldap-deployment.yaml +++ /dev/null @@ -1,147 +0,0 @@ ---- -# OpenLDAP Deployment for OpenCloud -# Provides external LDAP directory for user and group storage -# OpenLDAP Data Storage -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: openldap-data - namespace: opencloud -spec: - accessModes: - - ReadWriteOnce - storageClassName: hcloud-volumes-encrypted - resources: - requests: - storage: 10Gi ---- -# OpenLDAP Config Storage -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: openldap-config - namespace: opencloud -spec: - accessModes: - - ReadWriteOnce - storageClassName: hcloud-volumes-encrypted - resources: - requests: - storage: 1Gi ---- -# OpenLDAP Service -apiVersion: v1 -kind: Service -metadata: - name: openldap - namespace: opencloud -spec: - type: ClusterIP - selector: - app: openldap - ports: - - name: ldap - port: 389 - targetPort: 389 - protocol: TCP - - name: ldaps - port: 636 - targetPort: 636 - protocol: TCP ---- -# OpenLDAP Deployment -apiVersion: apps/v1 -kind: Deployment -metadata: - name: openldap - namespace: opencloud -spec: - replicas: 1 - selector: - matchLabels: - app: openldap - template: - metadata: - labels: - app: openldap - spec: - containers: - - name: openldap - image: osixia/openldap:1.5.0 - env: - # Organization and domain - - name: LDAP_ORGANISATION - value: "BasicStack" - - name: LDAP_DOMAIN - value: "basicstack.de" - - # Admin credentials - - name: LDAP_ADMIN_PASSWORD - valueFrom: - secretKeyRef: - name: openldap-admin-secret - key: admin-password - - # Config admin password - - name: LDAP_CONFIG_PASSWORD - valueFrom: - secretKeyRef: - name: openldap-admin-secret - key: config-password - - # Base DN will be: dc=basicstack,dc=de - # Admin DN will be: cn=admin,dc=basicstack,dc=de - - # Logging - - name: LDAP_LOG_LEVEL - value: "256" - - # TLS disabled for internal cluster communication - - name: LDAP_TLS - value: "false" - - # Remove default database and start fresh - - name: LDAP_REMOVE_CONFIG_AFTER_SETUP - value: "false" - - ports: - - containerPort: 389 - name: ldap - - containerPort: 636 - name: ldaps - - volumeMounts: - - name: openldap-data - mountPath: /var/lib/ldap - - name: openldap-config - mountPath: /etc/ldap/slapd.d - - resources: - requests: - memory: "256Mi" - cpu: "100m" - limits: - memory: "512Mi" - cpu: "500m" - - livenessProbe: - tcpSocket: - port: 389 - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - - readinessProbe: - tcpSocket: - port: 389 - initialDelaySeconds: 20 - periodSeconds: 5 - timeoutSeconds: 3 - - volumes: - - name: openldap-data - persistentVolumeClaim: - claimName: openldap-data - - name: openldap-config - persistentVolumeClaim: - claimName: openldap-config diff --git a/apps/openwebui/README.md b/apps/openwebui/README.md new file mode 100644 index 0000000..b4d1217 --- /dev/null +++ b/apps/openwebui/README.md @@ -0,0 +1,46 @@ +# Open WebUI (openwebui.basicstack.de) + +Self-hosted LLM front-end — [Open WebUI](https://openwebui.com/). + +## Stack + +- **App:** `ghcr.io/open-webui/open-webui:v0.11.3` (Deployment, 1 replica, `Recreate` strategy) +- **Database:** Postgres 16 + pgvector (`pgvector/pgvector:pg16`, StatefulSet, PVC 20Gi on `hcloud-volumes-encrypted`) +- **Vector DB:** `pgvector` in the same Postgres instance (`VECTOR_DB=pgvector`, extension auto-created on first start) +- **Cache / websocket manager:** Redis 7 (StatefulSet, PVC 2Gi on `hcloud-volumes-encrypted`, `WEBSOCKET_MANAGER=redis` on `db=1`) +- **Object storage:** Hetzner Object Storage bucket `openwebui-basicstack` (region `hel1`, path-style) +- **Auth:** Pocket ID OIDC — `Open WebUI` client, restricted to `openwebui_admins` / `openwebui_users`. Sign-up via OAuth only (`ENABLE_LOGIN_FORM=false`, `ENABLE_SIGNUP=false`) +- **Ingress:** Traefik, TLS via `letsencrypt-prod` (DNS-01) +- **Node placement:** all stateful pods pinned to Hetzner location `fsn1` (matches CSI-encrypted PVCs) + +## Files + +| File | Purpose | +| ---- | ------- | +| `namespace.yaml` | Namespace declaration | +| `postgres.yaml` | Postgres+pgvector StatefulSet + Service (fsn1-pinned) | +| `redis.yaml` | Redis StatefulSet + Service (fsn1-pinned) | +| `openwebui.yaml` | App Deployment + Service + Ingress (+ PVC for local cache) | +| `postgres-credentials-sealed.yaml` | Sealed Postgres user/db/password | +| `app-secrets-sealed.yaml` | Sealed `WEBUI_SECRET_KEY`, `DATABASE_URL`, `PGVECTOR_DB_URL`, `REDIS_URL`, `WEBSOCKET_REDIS_URL` | +| `oidc-sealed.yaml` | Sealed OIDC client id + secret | +| `s3-sealed.yaml` | Sealed S3 access key, secret, endpoint, region, bucket | + +## Deployment references + +- Argo Application: `apps/app-openwebui.yaml` +- Pocket ID OIDC client id: `3ffbd06b-12cc-4562-bbe5-da4433962870` (PKCE **enabled**, group restriction on, allowed groups: `openwebui_admins`, `openwebui_users`) +- Pocket ID groups: `openwebui_admins` (`737f4b7f-63f2-42b5-98bb-4e96c4a80546`), `openwebui_users` (`74b0af52-0942-4cb6-b37a-83d87d203e32`) +- OIDC redirect URI: `https://openwebui.basicstack.de/oauth/oidc/callback` +- Role mapping: OIDC `groups` claim → Open WebUI roles. `openwebui_admins` → admin, `openwebui_users` → user. +- Initial admin: `andreas.leinen@basicstack.de` (member of both groups; `OAUTH_ADMIN_ROLES=openwebui_admins`) +- DNS: `openwebui.basicstack.de` A → `138.199.128.63` (cluster ingress) +- S3 endpoint: `https://hel1.your-objectstorage.com` bucket `openwebui-basicstack` (CORS whitelisted to `https://openwebui.basicstack.de`). S3 credentials are shared with the Hetzner project (same key used for `outline-basicstack`, `basicstack-backup`). + +## Deferred / follow-ups + +- **Model providers (Ollama / OpenAI):** none wired yet. Add `OLLAMA_BASE_URL` or `OPENAI_API_BASE_URL`/`OPENAI_API_KEY` via a sealed secret when a backend is chosen. +- **RAG embeddings:** `RAG_EMBEDDING_ENGINE=default` (local). Switch to `openai` + `RAG_OPENAI_*` for hosted embeddings when a provider is picked. +- **Content extraction (Tika):** not deployed. Add `CONTENT_EXTRACTION_ENGINE=tika` + `TIKA_SERVER_URL` once a Tika service is available if PDF/Docx ingestion is required. +- **SMTP:** Open WebUI has no built-in mailer; not wired. +- **HA:** single replica with `Recreate` strategy; Redis websocket manager already in place, so scaling to N replicas only requires bumping `spec.replicas` (Postgres/Redis remain single-instance). diff --git a/apps/openwebui/app-secrets-sealed.yaml b/apps/openwebui/app-secrets-sealed.yaml new file mode 100644 index 0000000..4ea6e86 --- /dev/null +++ b/apps/openwebui/app-secrets-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: openwebui-app-secrets + namespace: openwebui +spec: + encryptedData: + DATABASE_URL: AgBZ3lRcF8KC9OApWa7g3oUoJG8DT8KG0/mgN6O1VwfnC5JZ9V5Lsp5sFtvYg5OXkFW4iBhzFu67n4zAR34EXwCSs977nt2uH7ShgZdrQSOlBTyaKO33ixOcY6WtGQVyJCzl0vjVHBKLwbsVkqEhBDLtqey1CZ9nAW/RZh8MahMSHhRNgJ4XtTyNK/5z2nmJyjW1oVwZab/cWeChlQUtJgt8qWxiQenlgu319P9plXrBDAPmDN/UKDbwu0ivH1s3sBQikuBYpCc9EJS+7NXBNnphsyJ90cF1oyqA5zt+0Q00fZnqE0qLdSOYbVCW058lK7NkeyecxeQwpFgPZ2XaLmU1rIl1wayYpYWCjZ9sLsImnIpFRz+0ouoWoKCXnJvgQ0inGjO5tCmMfHt0H6xTm/Etb3455raEujMcYbRD+mnC2UFmLHshuEb+X5JVUx+wfabVIFps8Vmf34OOf9yODvM127e8DdBtXQ5LAw9JP43tHT38+5XK9koCJIUtv0OQcsp9AAN5Y/BIPEloyt9iFWSQC/vNufYprzdeRGo5DkRvaJOij+s/nw3irOFw1oMF+V9+xTOfsgbxgn+gKJcNSyjHL2Z0gtm8oMLWHIxbjZHxpcY427cvqxITkb1am0QgNDLsTpO+Ie76qGtDiDdEZfgLNeGXaWbJEvIO6L71STj5RmdqSptMxquNIZkhGRz5XRaTupqcmyXAuIUCLIdejXMLBLXNjkOIPut6p8S3keGj3PdIO3AUFnLlpZt+idWMpn7sk07HKSj+qs3DdZcOpkHL0EH5A/qiNVXi9M1NoQgDqvEzF2KMYkvRyJJGwkyg+4snhb+rkV+ioQA= + PGVECTOR_DB_URL: AgAF2jka+S+kBgNuEGKupNdThqJesdGRTtcgo760wCMOza4yXJ/ivHMr8onLmykeZLV87Yv3rBKEYvssUciT/RD7QxCVEW31aKKPdi+MHg24/Fi3zXwQKoop05ksXizGVxWia5vrtG93j/XxWKi7JEb9glHe0Taj6TX0Hl+JsEBJELSTPrdngeOKzkYRFDvHP43AadUhvOFwxFoidbLGLulU9t3AdPpXb/T0wLNOcukZFpQxaB36MIIipaJl9leZ1sQ6C0e/MeimUYw3W5+JNf1P2lrL75dH/lqkkJZ0rLetSB7qRmpPyH8dMJumkv+16fkMJMt+LV64KdE2KdqtcxuXFO+RjYkMfzf8lFEtWPNA3qNuPXtwXZAfmLWEcjIJaTXv33Ed+3kAwiHil5Veh1q1eBhGQ0HKvkRGSnxxSyvVfoznLlLvNQVlNLgNfhd407K8ZOutushxaf/NJFW0RFdQcsq69SaQUFpIbv6IGaAM0TRN2O+qNatP3709U6dYQPYdGTJN4LOoKmfIIXaWhiqeYzOiP01QMhML0GvUVbwi3b3fK8twDx5O85Q8QSUgEOYkKnarSciUE/XYhQKi+E7Czv6vGbKmB3G5Oo9Bw6ONDvxd29kpkO2Ko9zwSFaqUooONy3PzkIwPiQ7Vp/ygsoFaVJNU1vsUWtDT8wJMVNcLPLCnd4ERhsMk/r+gY0SXZe3Slo4KIrqoAInf0Y1RBvKZ+XLQks1HxHmarA7uRe3Px/lMXm1kFRtlLwsNxwMUU5u65CbgehGphqq8UZRpByT8dFGhfnJK+rN4ORRX0Fd3fxgxtxyb5VgCe40rgQrH9LtdYculZDpRVc= + REDIS_URL: AgBuATUR1mMRwov35kA8dxOUqgl8O480/qxvsbBj3vt51Zo1s5cAzWBG0LlDLtAVh2NEAowSn+egj0mmLsAXJrndreQfAw+UpHDVHi4DbNbPeg7IUCivw/rMjLHyTMO4zsxqreKspwSNE38JU9vzgDBqv62HnQB7hzeGgpwUpsrWXiHNBji8xK/WJtIAGikbPEEzccDC0aCX9Ut/+Yb9ynv1AOtq/xMFIhsWTQ5r2vmbuMS/f59MNWMkWCZBApJL510+QUGzKXC/m553Mk9wHF4G+wX8dd2T4dCQxZ+A68gonvVeFiEYkSHzd5c7s+RYoSBGABrhJ7SziQOG5KWQSbXjjlu2o7JPmsxbgj9tpS0ErmCF/wzgEzd89DjtVNqJKV08nT8X4x3yb2L7oDtFPvEUZ9Ro+IpXYIR/ZnpXUcUgzIoaMJR9RsWBCrAKMFRlAtRsxWnHF0Mx+P6Di92mn2zyJ729FiB8QH6V2lmmko3xRhEZ9l7houxeoTG+37APzpD7U34VaO0W92ybvpcjfrL5BHZEXhpFqE3DXOXD21rf6fmxdOkNqJVJ45CXmweCbKzS3L/0l+GsTNrb8EZALj3RThcNMDgBTzpeTt4iHsodDXb3ziaVufbtw9BVCCrVNb01e2hemAgor5Z/TIkOxK7bLQhNTrpwvs2r7skhOUhzRNuusFi4tz1oFX0JO1Rj96X7Q37fQn5/fKkG6FeOtTIlLJ0mM8FoNijGiRkVgoU= + WEBSOCKET_REDIS_URL: AgBjDV7t/UTomB4Ml+7iX5pL1menP1c5kPZOSMlTBPaee6tXnPa7cdEs9/vu7PxfFtP03BczSoobXy4Q/4wvLe4sRAUHRQrcEk+hkJ7PbDT5n/K5zayaqlQJm5MQukytYe5EhlinsjR3EAphq5oCJB8NUAtdC0S+KQHckbxhUa96V7YEeHGu2U1oUJrZCCfQgdfHbFKY8FKm3lEd//uNxmmNovWcC+N4fwWmwj2Z9I68oNK3/H4E0GZwnL/1xxEDgaty68xB6wa7Cg3ZfeX9NLqZk0Xz5cyTziAPQ9rPwo/sShBnplDd7JZRR1podSASUEMhr904LRpbwHv+bfoEt6hUlq3g7gilF4gBVuMM0AwjWkdepFjLGSW++N7C16rei8l1uFthvVvMwdExd9dI8y2h+8ghkWfqwnFuomIxY9NgyAW4ijW7GmVfhKsnD0K9khUe8eiJtR4aqyuQWYsB7QkTMjuFPGZLe3qEg2q3Cl3h7H9tlQxUPqfvaN1Sb56rzQJV3dEMl55SAhoHrPzCjaTibZsuSkRW9djqEyc8lJ/PfTtln7aI0CG9h17WCc8cIAHHIhRIcGpNednIfmkDZjtrKVh415rdMMHZsX5W0/VRnKIkZK/nl0G2HDHw3MJVKaIk05mp0nPb7IFjE3i3t8p/jRgEhgdtIh/s0wma8OijzaXIbnKb8cpQ+AVYAh2TfEzU1bZuWRYzUck/lAzJ4xR4e/yWpsl9L/0tFN+TWco= + WEBUI_SECRET_KEY: AgBqvo0l+vmqUBvOtln8zf7UVDaVM739x2/xY3lCf/i1/GlmCK/3aGDLj6tqODFSXuKTKxMS3liFVDleR/7EdYvu9/7H0ThSfO2xhheLzXdooxpIGHnfriKL58UpyWcieo3Y6VIOAQzrSyjBSd6S3GHxjtBdqyqvuGSUXkojf+jOjOKC2yOf+0qySklm/LAww8MigPrM55rlUcqRwb0D/KdpAIxw/wplkAMdFz0RGGfF2Xr03K0AKqJ2egUb9exI0JDUyGQBDJv9isVQPr+2xZHvoSb1QcPCExL5FA4YLN/EhwKhlp56itlrGT44aT3Cx6e8k8cEeqUyP0/4Fjz6xZnQLnTt1dvFAmEAusT9riqTkcAqhHR87MmkPIYmlJLkud1aT8G3LpXUmghrQgYaam5G4DKbQbQ1SBJ5eNC19VNW7hcL5zOvdB+Vm41/OzM4n2AW+Oa0UjFMgWO27IVIGVNjwn2VKapUWsRHLkidGfeVQIYyWznXRFPiBhSwvHhO767Bt1TASst3R3f3bZDcjASLr+0VfRm0MbJ0iXZkK8IrQk0un8zHEZRwCWk5Jl/LM0AnS5NeRgO9XAPlXm8xv5Xy8D2DKsIZGMkXZWeoEJ57/l2i/mn9JMWhq+va5+jyHKQwrqAH9ZqZpWvmip+LeIrl/AWiKGSzarhSIw3DLin+HTJs0rNswVdU4FgA7CDi1Dg1rTW1bbDMrebSB8KNLYqrWlokW4MQ2d9FuzrN+UhMYKbiq/TOS26/oBlWvMtdyO2qVTHjWJPWUDpYny1yAVHJ + template: + metadata: + creationTimestamp: null + name: openwebui-app-secrets + namespace: openwebui diff --git a/apps/openwebui/namespace.yaml b/apps/openwebui/namespace.yaml new file mode 100644 index 0000000..586b99b --- /dev/null +++ b/apps/openwebui/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: openwebui + labels: + name: openwebui diff --git a/apps/openwebui/oidc-sealed.yaml b/apps/openwebui/oidc-sealed.yaml new file mode 100644 index 0000000..0c54802 --- /dev/null +++ b/apps/openwebui/oidc-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: openwebui-oidc + namespace: openwebui +spec: + encryptedData: + client-id: AgBPXc6HeT9gaqtXK9W/Bo0Jgt7UA/2Aygb7bHmwXSJHjv00Ja/ciISsmHQD7fovmG6eoM/fi3fRdJZqJkekRX+3Od+09I3BYQGzXjNwBkplydPLPEuzqDcCLdRTqZCpDFIz3If08++23hyeH233Vp6BwDxcZXunqzzLMNY+i907QhGm0ULb+T54zmZ49NBElXv8/moD9cag807ySCDw+I5pcWQMIZRv1FKtso8aeA/KA8Mzgfmbhxe+3HpPZ5svuOhFlzHED8bZ39c1l8cakjPDuRvrrIXzwogNhuIUArmXIyBhgq9G5W0yTsvqrsep1gMWEtYnZSNgVkK3vzodH2oOwstsOMgKThd1c5bnIPbN3bkLxwd/TbyHmQdLSCMNwOCVo8XGbeRhSABMxVacLfDI7hR+WIyRdkiMwKKQnQMqdBynzcWWXeSlnzIBpS3N+oodO7gS+l+6G6KX2ZXyUyRlxdDo7m9BJhc+HhxJ66L8vVj1P8QKczrixWJUqrUIFxM3jKY1A7XOI/x20/gQeodCJWIHetKvIHwwQh+w63mwLP8gRFglBnjNFok2zeYNBo3eSv7zhXEYve0y83al86/TrLggSfI7juJmLcy3bCy98ZExXdf818JslqmDutavUkUeYn8wIvdkeZbRtWgYLCu7izJZ1UDN8zkP6VQuhzLHO2OrhUuKnwpiI7Jp4/gIrwuGI9Ahal1OX+8T6R8JrAd4lfz5MfyoNzFZ7qrp7xe5zlTZZxE= + client-secret: AgABqIXEErUfaN5XW7SxGr/yJgs4GoPufPmNZF4d34STcCi7s+kE181h985ubaL12mwt6zNTDxUlGdz5p7ABLePRf8gEMUtzFNom6BOqIiJjhQuWLJRR1//DIfu+9G1KjmSFKS4iLkoHV7bh2+b+q+u3NXFVfClV8bVMICN1Nzp+doSdwTAh2CyQTK5JXmu6jXDJxuOkJClQdEH+rwdE5/It8ZHhC3y568m5H1JO4dxgvBvZv7jLxX8b+hGGLD8N9P6zMRwiKUlup+UPCv1V/SjppAP5kLmPKumqhtWqEY0j4XIu6xsIaZz5WeIAzlpQoRLshd1VGk9ebfXN5SfXcJdHyZlH5zy2W9HL/iBmr5kv2RkzpH2Sludewk2pWPatXtEcE4N1sat1cR2TnfW4wxJySZ2ni2d7H5jPFNu6Md7MFVVneYhanSdt++CtWol5bikEkzuSk2kjPT7Bf1wRhkz68VLo3Faf6ltktvpHV6eGfAegW8HgTSYjzwA55MdqWNrLlN/iZfcfhR6+8A2xblpUFXBgvCkTzLQzM2d7gK9mr1j+6osWpUDzD+Os8NSsnPBM4ywm2xfW5ZSEHdX2TmF9s7hDLCffQfd+ILSre/uyxNXuSNesE4pC0bFF9OndE2IvIQkuMPy306NnCEBzZsp+P8J0NHIr7EKWg+IMFRsYMQrIl5Gv+opyTssr9fTMAUN9ns0LdQPxBnjce+0egHV6g27zg3QVIc6K6vsZNciYyQ== + template: + metadata: + creationTimestamp: null + name: openwebui-oidc + namespace: openwebui diff --git a/apps/openwebui/openwebui.yaml b/apps/openwebui/openwebui.yaml new file mode 100644 index 0000000..c46bb2e --- /dev/null +++ b/apps/openwebui/openwebui.yaml @@ -0,0 +1,279 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: openwebui-data + namespace: openwebui +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openwebui + namespace: openwebui + labels: + app: openwebui +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: openwebui + template: + metadata: + labels: + app: openwebui + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h openwebui-postgres -p 5432 -U "$POSTGRES_USER"; do + echo "Waiting for openwebui-postgres..."; sleep 2; + done + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: openwebui-postgres-credentials + key: POSTGRES_USER + - name: wait-for-redis + image: redis:7-alpine + command: + - sh + - -c + - | + until redis-cli -h openwebui-redis -p 6379 ping | grep -q PONG; do + echo "Waiting for openwebui-redis..."; sleep 2; + done + containers: + - name: openwebui + image: ghcr.io/open-webui/open-webui:v0.11.3 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 8080 + env: + # Core + - name: PORT + value: "8080" + - name: WEBUI_URL + value: "https://openwebui.basicstack.de" + - name: WEBUI_SECRET_KEY + valueFrom: + secretKeyRef: + name: openwebui-app-secrets + key: WEBUI_SECRET_KEY + - name: ENV + value: "prod" + - name: WEBUI_AUTH + value: "true" + - name: ENABLE_SIGNUP + value: "false" + - name: ENABLE_LOGIN_FORM + value: "false" + # Database (Postgres) + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: openwebui-app-secrets + key: DATABASE_URL + # Vector DB (pgvector) + - name: VECTOR_DB + value: "pgvector" + - name: PGVECTOR_DB_URL + valueFrom: + secretKeyRef: + name: openwebui-app-secrets + key: PGVECTOR_DB_URL + - name: PGVECTOR_CREATE_EXTENSION + value: "true" + # Redis (main + websocket manager) + - name: REDIS_URL + valueFrom: + secretKeyRef: + name: openwebui-app-secrets + key: REDIS_URL + - name: ENABLE_WEBSOCKET_SUPPORT + value: "true" + - name: WEBSOCKET_MANAGER + value: "redis" + - name: WEBSOCKET_REDIS_URL + valueFrom: + secretKeyRef: + name: openwebui-app-secrets + key: WEBSOCKET_REDIS_URL + # Object Storage (Hetzner S3-compatible) + - name: STORAGE_PROVIDER + value: "s3" + - name: S3_BUCKET_NAME + valueFrom: + secretKeyRef: + name: openwebui-s3 + key: bucket + - name: S3_REGION_NAME + valueFrom: + secretKeyRef: + name: openwebui-s3 + key: region + - name: S3_ENDPOINT_URL + valueFrom: + secretKeyRef: + name: openwebui-s3 + key: endpoint + - name: S3_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: openwebui-s3 + key: access-key + - name: S3_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: openwebui-s3 + key: secret-key + - name: S3_ADDRESSING_STYLE + value: "path" + - name: S3_USE_ACCELERATE_ENDPOINT + value: "false" + # OIDC (Pocket ID) + - name: ENABLE_OAUTH_SIGNUP + value: "true" + - name: OAUTH_MERGE_ACCOUNTS_BY_EMAIL + value: "true" + - name: OAUTH_UPDATE_PICTURE_ON_LOGIN + value: "true" + - name: DEFAULT_USER_ROLE + value: "user" + - name: OAUTH_PROVIDER_NAME + value: "Pocket ID" + - name: OPENID_PROVIDER_URL + value: "https://auth.basicstack.de/.well-known/openid-configuration" + - name: OAUTH_SCOPES + value: "openid email profile groups" + - name: OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: openwebui-oidc + key: client-id + - name: OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: openwebui-oidc + key: client-secret + - name: ENABLE_OAUTH_ROLE_MANAGEMENT + value: "true" + - name: ENABLE_OAUTH_GROUP_MANAGEMENT + value: "true" + - name: ENABLE_OAUTH_GROUP_CREATION + value: "true" + - name: OAUTH_ROLES_CLAIM + value: "groups" + - name: OAUTH_ALLOWED_ROLES + value: "openwebui_users,openwebui_admins" + - name: OAUTH_ADMIN_ROLES + value: "openwebui_admins" + - name: OAUTH_GROUPS_CLAIM + value: "groups" + # Runtime + - name: UVICORN_WORKERS + value: "1" + volumeMounts: + - name: openwebui-data + mountPath: /app/backend/data + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: 2000m + memory: 4Gi + startupProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 60 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 5 + readinessProbe: + httpGet: + path: /health/db + port: 8080 + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + volumes: + - name: openwebui-data + persistentVolumeClaim: + claimName: openwebui-data +--- +apiVersion: v1 +kind: Service +metadata: + name: openwebui + namespace: openwebui + labels: + app: openwebui +spec: + type: ClusterIP + selector: + app: openwebui + ports: + - name: http + port: 80 + targetPort: 8080 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: openwebui + namespace: openwebui + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: traefik + tls: + - hosts: + - openwebui.basicstack.de + secretName: openwebui-tls + rules: + - host: openwebui.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: openwebui + port: + number: 80 diff --git a/apps/openwebui/postgres-credentials-sealed.yaml b/apps/openwebui/postgres-credentials-sealed.yaml new file mode 100644 index 0000000..69eb54c --- /dev/null +++ b/apps/openwebui/postgres-credentials-sealed.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: openwebui-postgres-credentials + namespace: openwebui +spec: + encryptedData: + POSTGRES_DB: AgC27bglkH6N5Ule5F23XbirvuqWgiQlwd8yyqEqpcZYsvT8605y70g2mS4zZBtvVqdYFcORJF5abdqWdVhmPhZAV71tesJBTOynMQb9Zd39EbBcEb/6Shresa7FdaLQgaQGLxIOOt1xWLmh/eRv9bLiIGNtz1ezKCa9fXYuCrD1Eg0EGmUjdioXbkuVoVNON5rBwUTWWpfVq7/d/UNQGUvAuhOX2RORD4hy/5IRZG5LvAF7C9oK1LaCbi5B65kbC9DOtlCPfB499tt8fSQDYic8YBLkmPKQgHd3+8NlNrH+TtTZMTO6UOR53IqJW/xxoJZapAl/40XkFj7Q59X+xDzG+p81DfwwOu9U+Fu5a2zpdduCAVQCIRSVZBL/ziYkRjFR08/5ToqHF9+p0C6khIgt1EkyvyUTxZfKPlcq68soY9cl4UzV3UPe6DyyEXwKsPmaUXPG4ckGEAWIIIoEd4M2cFN6jyWTRyYiWfe9uaPSIkjBGi8TIXk+Tk2ZTcZG5UQd48MOtc9gnXQwoFo2JzAqqBYa4HcATzPPiDzQCKhMXjGelINzExqvjOm26L8Zi5yCsaAoP3Ev/Kedl3ohdWW7CpwTxcffnd+CTYo5oz9K8HD0CvS2gc46HL2zCzZSF8Zpap/0bsM4FWFcEEI8ru9mdkhXZ9EygI97fcxtAi5eDtYm2UjKRDzCLLb3a+eorUEKfpXPUTG5E8A= + POSTGRES_PASSWORD: AgAislLVvgaIxtm+U9LzD1lK0Rz05MaiphMWHuHVpGayAqTorWy7jz+iSI4aQH7QQFzClv6WbSgzcwSjYiTZhH9+GxpRHpIAcyYbjHVG8qU3n99A8WXXCd5V8K4alQK4z5kA6OxZGOuYRQ2mfAbHGdAUBNIVTqNv7DtpNuATKzZbkLd/tMX3ezIcpJqOEtb0AqgMvcH0gsw2j7I04WBlCdH7qQ6e8EUqHjxWBc1YMF7INzAxlkVgIcaNkpfcErEL4y7X1E1yo0KRzAg0O18rzUqs+hOi1XVSNaEuA0yno+a1ZXzGnmiwJFK+BboL3BZUtev6Xlbe3DqcCwUjmw3v18suO84gXGfLEJa/OYsys1vgHQ4qt0tP9guSWaqnTfdkc2ZVilw7M+d2TS5PtQC0U2/MW+SNmS8EdC+/q6FvTgaMr2uptqgC6hrrLFemQ1KFzJibut1s5o7HuLKqD915gSbHTPJ0vtPOstIJo11ggmvLw37JGqhbHkME7SwkJzZXWAFPvAWfsJTRqL8fzdk1RLlVYQeak7VnX/iL5q9bUomsGvYXMV90/DH3vdkhFuxOUx1kdjnROtx8PYg3rnzXPvfR49+x4EhUWRqaF1XDXWdWhCKrZFrSskvcm7h01gHvLQBhbKK/X0nOz6CM/Ht8zzIsnsmjssoZaz7iFrvYYyhSWyp/U/N9GdFkcDA6qzRsZSM8WGnRnEeQIHklzIL1fZlvsPl57rFjHSYZzdXD1Iolzn9v8KU4vt7kGxOiPh6Abfg= + POSTGRES_USER: AgC5pjM5LU5zyzqvfsrf1u5koa/IwO0wTnDUv4GcEzRLKD/d0Z5iDlT6bdyCS1bFr159NWUyz0FyqfMDqsGFzJiGwEd6Bq2PHaaa+h5wfhY/KguVPndb2SCRJWDcxddH85GGRo6iTkVLeNBO5mOI3imQnDNyKaBpowRINoLNnWDoTEZ7COrTrLTWBlJz8ar4JyIWAToS08cBIhQqIBCWJkUpXSS52Frju3yYKBCeR0lDBrL97WghqnM7cD38luPXajLZeysck8iAwZm2Wt57ZmLUCLSo9cxkhL7Wx/mV2A/s43KzL0m7w+YkQsk6x8anug7+hr2lKAzb58ytKkyPsJ/OirGB9fRfIujEyU6B75AFDePtvv5W6Nf3ngWn4hH25K9dDA+LyMKo0KVXQi5uMnVFitwMUMRFLFlnyreydt/aQyG/G+XjE1TZtBgjF2OHdPKSaxqOh9F2vZ8GLRNV70EVVOO54/4lqeLChNRBbuxgn5v1MtD8lOUMtoS4Ho+82995MARCzZeukfP61tqMbKvTn9HtbbQq7jBfoWCUgCY4KeiQ9sf5+Ks9jfmgDvdgBckiO0IJCq0oaiVvUGW5fkLwa273sypVo6sglLoa1I+YCkUJ+Ym7nOw51r+zATrNmzzAG8W04ynz5Jx6iF6mS0K29giqAx0POxFpw72CPI67COYLQdXr4b/UoSAlWWwCH0eGCJUfcA/LYIE= + template: + metadata: + creationTimestamp: null + name: openwebui-postgres-credentials + namespace: openwebui diff --git a/apps/openwebui/postgres.yaml b/apps/openwebui/postgres.yaml new file mode 100644 index 0000000..965fa7b --- /dev/null +++ b/apps/openwebui/postgres.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: openwebui-postgres + namespace: openwebui + labels: + app: openwebui-postgres +spec: + type: ClusterIP + selector: + app: openwebui-postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + protocol: TCP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: openwebui-postgres + namespace: openwebui + labels: + app: openwebui-postgres +spec: + serviceName: openwebui-postgres + replicas: 1 + selector: + matchLabels: + app: openwebui-postgres + template: + metadata: + labels: + app: openwebui-postgres + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: postgres + image: pgvector/pgvector:pg16 + imagePullPolicy: IfNotPresent + ports: + - name: postgres + containerPort: 5432 + protocol: TCP + envFrom: + - secretRef: + name: openwebui-postgres-credentials + env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + subPath: pgdata + volumeClaimTemplates: + - metadata: + name: postgres-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 20Gi diff --git a/apps/openwebui/redis.yaml b/apps/openwebui/redis.yaml new file mode 100644 index 0000000..21e4da0 --- /dev/null +++ b/apps/openwebui/redis.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: openwebui-redis + namespace: openwebui + labels: + app: openwebui-redis +spec: + type: ClusterIP + selector: + app: openwebui-redis + ports: + - name: redis + port: 6379 + targetPort: 6379 + protocol: TCP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: openwebui-redis + namespace: openwebui + labels: + app: openwebui-redis +spec: + serviceName: openwebui-redis + replicas: 1 + selector: + matchLabels: + app: openwebui-redis + template: + metadata: + labels: + app: openwebui-redis + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: redis + image: redis:7-alpine + imagePullPolicy: IfNotPresent + args: + - --appendonly + - "yes" + - --save + - "60" + - "1000" + ports: + - name: redis + containerPort: 6379 + protocol: TCP + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + readinessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 15 + periodSeconds: 30 + volumeMounts: + - name: redis-data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: redis-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 2Gi diff --git a/apps/openwebui/s3-sealed.yaml b/apps/openwebui/s3-sealed.yaml new file mode 100644 index 0000000..baf64ca --- /dev/null +++ b/apps/openwebui/s3-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: openwebui-s3 + namespace: openwebui +spec: + encryptedData: + access-key: AgDBruwsmBhUuqOg34A8jWTT+9Ko9K4erxigpVtcQRxfvoBu0Jn9O/W80XDPO4+HD6krMlxZyPb0uP/qwsLU99Ll2gcxsUDVOvhJwkqzI1ohZbl+iueJ7qG7Ty5HO8CKCe8AiDgXnEW5/hWUxYT+0lX23aIv3SaJdhOHaScMCw5DSGlR4/JA43uEfK+Rbx+FDYokw17pi60LAhuQhZpbzoy0sgd4A5nKaSyNRis/YPnIZzN5gb8yyuGYa5fHSLGnowsuAlfj4x1MzNsw8pzuygnti6KsoMJyWnqW1TsaWb8WkhWDRKEN2AO4tTD+2BuvixV6LBynomkLfwZ8DSmN0Di5Db3bKGEkECiRAloeIc4efr5L5W4ucAxPE1YFOM7VINt5k/7pdymQapGRyQymzEsC6dWZNl54SjN65zzZIerkM2N2II0oqyzhtYW9WZ1ihPYvXSSp2JoaiKny1ljzqyXeizX/Xkl4ZjUZETYV8KIIFMx3SwTGBvShGwmv3T/v7XnMDxG7lIvjtHvCmcaiW3fegz3reckuczuG/c4rtKQo+bzPnkWznKlXVrHNg7Hks9DisIQPngn/u1bJPK8ncEYaX0MRGIIqb5yn8eoT9Ggxk9eY7QHym9Y/bjFMbR5MMR7I5fSQSP2cUI1K6OQ8eqkteCUB2i0wCfDXHqnkE44NJM38hWUl8b3rNH2yo2gnLyYhkaeqdetqNbW992quRUp7UXshrA== + bucket: AgAm7zODlaJ10GoHumXsgXJC4An3GNegi/Gm3aNhKlyNV3ItVU8q43/UPMck3ed14XZ1gbMUIkkIU8L9IIIofwEam4YrfQYfoQomtaYF2EHnRjH56+xrnRP+WKzTbqDpiZIcRfr6E1+ajBM70H7A0TfztLiPmFNJaGv8YPLSdWh8E5m9vgiJJ4ErEDwMxMrhLpfjZ6iNp/H9vchPLOtSP3CtAJvrrdWO8My1xZX3bnMpoU9YLlSSfPAGpglZ5dsxeRBA+HSSRo4PouhLKMm7PULk4eFFwtetNgPh0DwHXhD7hboRcZJECmZMPz2xdWwu8+KqKmMywTaN+gy6IDOw7pZV0hHwVd2AWhlE5dXnV5pVEm0vUKbC5ceS4Swq27RphO5g5/7lfnkV1I9JqTPETFXCydxQ+vhW8UxbEunTWp7PxE/XCNMvYBvecksWEmLix3iVFGj2chH2tnDLx/m/K2HxQkqNo25c1PnDO3aL5WTSjcK9VWSwrv4ucoROtDA4MiD33FCSF4eBwzUm6AUPuWIIsoobjGOKZcwNiRDDtom6O52h6+0/xdplrNdj5X/5GHe7jnEM/IwaYdNsYBu0/Hi9eohA453brJfGzShe9mcKw0TWktq9xrmfk57PuWAKny6dg6VWixS0YHNPd7v1EkANmnm1nagNMcurScolwgY/MXYEbtQbbHPk5Zn/yQGrDCFoHSkE83gC7go9dTvnmEi4anDfYQ== + endpoint: AgAlRlwvCwNLdPXgWgpSYTz7MhtX+wsK9TNCep0C6oGJi6HMlEzT8s1YW+58HTGmc5wFkoB8P48JDwcfdb7BjAgFMVgkiBz3WrbtYYgUda8EeiD+5Kt04lTcetPibi/R/sbe1Hvxp2SIsJ4Qgt6x6qElR0GM3hfiavP95mazIL6zqX0MImXtKFEqGBwhSiFIQ2w0ldbbBxpl2B0w9JpOO0f9TxEZW4do4JyKkTSfUnBpJdpN1tqDP+j18PV/H7s7O4zdpVWmwlp8l96xSVZtg0VZvVvdC201mPuAKF7EV4bIy6/a6jIgKeUv0k0aeZqqTgeNkGmCzxI43ujShMxVEuNaDDVt1PAzcTl2r8IImyJlf/B3AipgyGpwXUzn2+A0DiBIqjOXC1dezkezS0+iq5yekO20d3N73KswIsWJu25Dvc5j+c97nPSQmOYGcoYtZmoIac2GNf+UTApbLiJdzSaP0hQZ4c0p6g+xtCMjesLl1xI1ihMOJtetW/mDsKokGWfACWcNdkN4mKbgBHtOS8GvfK13NXpDRDfgl95avhVgj0hdGANre3fSk7cmEX4fcFCyyj3wUqHOBK98oNL8zaNVmHjEDoajw5RI6EwHOM1lnnUfMyEuJT+Ye6vRCmJTLoMrVqCOUkcX0rfIA7+VRJjSoeeoz7+iFJcsNGH6HIdE6rjOH9HdC4v/L5wNH5CUbiPLG8tFcFiFF2k9EUg6V2YymkrgCM8WZb2PN0QQloohe1VkRQ== + region: AgBWLZOWPXCo8H5D9fKTqK/UWSOGsB32Swr0H3mhoOvZntLkqtxR0WsToH1hYzLnsVz41O5ntBRiw4nx00SvKs8MdKT3rW2gcuJcJMznUMXN8WoWLfUIrRHO5xRR4bh3BDeqaJo80iGL0Ql1/460BYwf3F5mBnKDEBdbaJCNKraRGLUVSOJlFFkebzwTsgOszQgOOZFFA3oRUNgZugHFzeOq9HYYqf+XQ1WSA5AyKjgR2xYK55vOiw/qag1keX31kWGgB039M5IcNhdI1kVUrQWgBeDWU4zp30983FFBaxJENKqTXEhtkfJGgi33+kPuMGdz7KQfNShd6bQNVpfGeX9ZsTPY+ZIwlsiljQzuJ2FTu84Xnmf3jNsoDuOnq4BmpYHV7+t9iGPFmiiTHCYN5hTlNWptLvBa0ZYE1dOwEz41ZEEisj8RAMWBNBiHZDBPyVEvWjkmld0Lro71F6XGb50jlaQlOV31b3zVksatzgnkQmtNfeZucHY6AyxIrwlbw+UxTIJ1dfzFBRyrPAdGZamUgyqZLqXWkJmwy6ktD/tRJrfpso3QjkPjAlCXYWNIp34Qx7GHAf4t8jaCJx+qjlpSuQ4iv28xFL5xug0I02cfvZwV441ANVP5i4ATnVpJ/TIb1zbUtl5ffuGrD5fY58DQ74ntsHSQ8h8JIpQDAu7WL+ZSWjGpV5tBUz3vIP4wsDJnHN7p + secret-key: AgAHkBSjTxORjuS5i75zd5Sk3FytIYwfQTgRs64LUHl2SAD86znxUh+4T+AGCO6dZHoKp+madkx84ghUn31LdA/2FRfXrrujxHLlom4LKHkv/jDc4hSRtJPMBqpi0KUXUtXk83nJ1s1BN5er93S60IiaYZOBcS0XojTfh+TbwlKJJsKstJcOB1PNVb0V4/OC+fSWpOJNkSI5PAEkzVVcSFsEaIuIRpAazwm7hBbnvWCGwd/WlSzZEHNVnf/fpI2QstVYGe42fg0NzEqnFk/I5QGRcKhUvM3wQ5smNidb2mors8FE0UzdvFcP4cytYb7tDf0GKclX4p2kmc7Js7EWTRmin1N31EVdGPGsJ2sV5u5JQoAg0b381H4ZqQckInB8NIoAx5L2p1gqnPKPt2yTV192jes7COzNhmODTVaSKY0z3v//u6tVpsanfiDvkSl4y47BKeJfxp4Ihd4wJNgN3vkaxi4mDYBiNiY2QXSczZ+2qyacZeGowl1ZpGH13KSg6bOV/hQ/T0tr0lbwiZ04V7v5pORAw8SnvHT6rqzHLrE6PxDiLQsY36bIpMvqPLNke0ErEfqXMwGeYClij0fpU/7qZ5t9Ag2PvClroxY3wuLdpKE9ffeHtH50BgyabOS3DsMXcB1pODyXf2QaEzB5NaGqMp/PEbmoy3JLYwM9We5NCQObxfjEI6gkgBitBY1PDBbU6DGfTM3Smf8y6qJ6Y4kHx31VUU2eVHP7+O912OH9yzguyq1DpkiI + template: + metadata: + creationTimestamp: null + name: openwebui-s3 + namespace: openwebui diff --git a/apps/outline/README.md b/apps/outline/README.md new file mode 100644 index 0000000..a183944 --- /dev/null +++ b/apps/outline/README.md @@ -0,0 +1,39 @@ +# Outline (outline.basicstack.de) + +Self-hosted knowledge base — [Outline](https://www.getoutline.com/). + +## Stack + +- **App:** `outlinewiki/outline:1.10.0` (Deployment, 1 replica, `Recreate` strategy) +- **Database:** Postgres 16 (StatefulSet, PVC 10Gi on `hcloud-volumes-encrypted`) +- **Cache/queue:** Redis 7 (StatefulSet, PVC 2Gi on `hcloud-volumes-encrypted`) +- **Object storage:** Hetzner Object Storage bucket `outline-basicstack` (region `hel1`) +- **Auth:** Pocket ID OIDC (`Outline` client, restricted to `outline_admins` / `outline_members`) +- **Ingress:** Traefik, TLS via `letsencrypt-prod` (DNS-01) +- **Node placement:** stateful pods pinned to Hetzner location `fsn1` (matches CSI-encrypted PVCs) + +## Files + +| File | Purpose | +| ---- | ------- | +| `namespace.yaml` | Namespace declaration | +| `postgres.yaml` | Postgres StatefulSet + Service (fsn1-pinned) | +| `redis.yaml` | Redis StatefulSet + Service (fsn1-pinned) | +| `outline.yaml` | App Deployment + Service + Ingress (+ PVC for local cache) | +| `postgres-credentials-sealed.yaml` | Sealed Postgres user/db/password | +| `app-secrets-sealed.yaml` | Sealed `SECRET_KEY`, `UTILS_SECRET`, `DATABASE_URL`, `REDIS_URL` | +| `oidc-sealed.yaml` | Sealed OIDC client id + secret | +| `s3-sealed.yaml` | Sealed S3 access key, secret, endpoint, region, bucket | + +## Deployment references + +- Argo Application: `apps/app-outline.yaml` +- Pocket ID OIDC client id: `b4a84bbd-fae4-445e-868e-5c1197e5d155` (PKCE **disabled** — Outline's OIDC plugin does not send `code_challenge`; group restriction on, allowed groups: `outline_admins`, `outline_members`) +- Pocket ID groups: `outline_admins` (`2b41fc67-eb7b-43d8-8954-85426214f1a8`), `outline_members` (`aa6df56f-b868-4de1-adf7-8696e990aa53`) +- DNS: `outline.basicstack.de` A → `138.199.128.63` (cluster ingress) +- S3 endpoint: `https://hel1.your-objectstorage.com` bucket `outline-basicstack` (CORS whitelisted to `https://outline.basicstack.de`) + +## Deferred items + +- **SMTP:** Outline runs without email notifications. To enable, create/reuse a Stalwart mailbox (e.g. `outline@basicstack.de`) and add a sealed `outline-smtp` secret with `smtp-host`, `smtp-port`, `smtp-username`, `smtp-password`, then extend `outline.yaml` with the `SMTP_*` env vars. +- **Admin promotion:** first Pocket ID user in `outline_admins` becomes the initial team owner via first-login. If the admin role does not stick automatically, promote in Settings → Members after login. diff --git a/apps/outline/app-secrets-sealed.yaml b/apps/outline/app-secrets-sealed.yaml new file mode 100644 index 0000000..57ea115 --- /dev/null +++ b/apps/outline/app-secrets-sealed.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: outline-app-secrets + namespace: outline +spec: + encryptedData: + DATABASE_URL: AgA0XHjlu1iWM7AJaStmEtMdVtYMwKzXxk7/nMCtIaNxaXTa7XH3K40277Wc5a/mfM4KLiF6iLExquNIa7YI6R3EROpUC23cTJbstpQNP6tffzZwr3FeTeod4qWYJ3HfIHaBlocNc0p1ntM9H/9k+Vy3rQCn9T5kLJ1c6LqUGhPinWwQxwiDgNU6JQyEfILIAX35th5fe24GH4Ka10k56wq5Fnx9CkYYxsVQ21NNKCdtqZecR+C2RqfYhXharvtio1ojWP4KNQZuXEXGDAH/nb7QPtaNPCV8hhsS66FAjm3hHTOmhJ4XXbJQpWYCiPOWBa9MVHxMg3+IDfdR2OWLDOpgFzN/vMKx+iqa3aVHXfdrXcFUqEC48d0h3MXjdCge4c8wY469PUTar+74d4BcljjG6R1FL5CJ1Wmxygt6rG2Qk+pWihZ00Frc0gttsxowS7c9s8iPL6VF1ShyaTXdYq0W7pHlX/Yq927C7h8E6TNQ+Rxw3UI4qPXN7xkU85FTQvWZjqoyyej5GaPbGy7Qp/SO52wsq4Fh5qPrZKoGl5xQVcmuF4LZKbb8WQDQcFDatHdWZiHZqsX1wKxRP0jR8RkT3Q7Z/M2itMN9lODtddCv/hjrwd1ih950kaZoVW3cdZCVb+S/M4pbkuB0BAasRbntJx/hOy/9zp21MAWfTGTyscBetIjHeC/oy4S1WQyo5TwkMSeugqXJ/mn/zTEpDYrxuqso7Aeu9H658WeenwgUVh2VB9SVWFWxZnHe0rB+keMPDwJ7Dx7NsdydybsRmORtTIc4zj+OsqDhmg0wwCq6pSslp+/h9MwfUB8YkYv9ioPP + REDIS_URL: AgCKT7MxyQJV4Yk+I8TO52x8x5nfXd0ECB9H2w8p2gRFMyiTbrTMvdzaF9fqrgTiGRuB3VgDeeQpsnFGvemoYWqbLZ+WI1UfQKlyar5y3tHh88GMoI53dmkaBc6V7Fr353rkcy9NmRJC+g37V5PWasiX5aWFSdYoslmaUOC/o4pQwyDZEwo3xFHysbm9tD5jGVSsCgnUGeiG0Urva8pNrM3aTfvSoVOhts312eKk3+x+8mda7ZchH1DtQ9EZ1V8emEEjOv1Jh6gh5wLjl710cHAS0ch/9RTj8ooCH//pX4V5gj0ue5EWL1vk0xgV+kv5tGG2pwR4aUZWa1d1eQzbFDshHwkbAWKm0pjCq6eIMg6QLQRdagGXe4ZKsRAahUJ+7Qo1MqDXthoJHDEDDhB+/SbbPKTOrgm+eh3oA3EuWDcp+iKAqboBc1WkzUWh7kRVRt4bkVBHkeK3UjFvjGtuwC8cI2yOQ3DyRh7Ija+Fp1o8ust86FKYS1/VUQRqBX2HTBamognfS+EN4PyrHJ1XZzoL85njGm7v6nOG+EQN5QTlRkmLTLqLVWzUk9tuQbpm57r0ULqbkMKYzfnaj6opWo6EsCH2uQg2KGMVgxAhY6jFuFc8cLXdz44Fb7tjTlSgq1Z5S3hqHRrP2fd5upjFICP/ECa+JRb6ZGpTSvIJ2MpH/0oCTFkgl5+A2KUUP7PDNkqCoVhujRLS05XuSize+owTbjeEz7fJCRy66A== + SECRET_KEY: AgBYcN97oeke6wlfUel6cki6sYIUyM2zJUuQfBSSFoZRawSurDhL7MboCbVXXtgold8QMp1aF541442TO0DdR1eMvOvNqGHIQog3sNDlbjr3LOk3vV7FcZFoME866tsQ0uzfIR5Z7SBvkD6EqdnrIe+sNfSQHt1Jn3/QCPhgiYbsN4tPD1t7HFAuuPmI/gs91wnv40r7PGxK2cYYcPgGrT1P+prjSsca5sbVvy2e/jkW8FWLoRxrNw4Q7FImgDPP180Adt2V0n+7HfY8u5gSS94oY+5CBmWuR7W6GhStuI/LfKDDVVgWdKtnP90Eq9NiGRg1Xk+EwjNLWSm9aTLZ/2h1jzDGqmrJ75W+Z5OHV2bdgxCFb7lZJRU1mDTCjqMNf1jKEzSQ2pFYHHmyRhz0dQ7FVD7wUS3SSTkC8epxxRf+s8Jb2O67x5sM87V/QPyMGlCPs/634OOwj4+7V8owcc+NaC9iiGTjwd+6AurFCF3O2t2YcYgsJXoDp7Rd0hf/kk3cEKAhKMqO2DDAew9Og3fpJrF2mgYnMJb5fDAaEq7FrA8y7P9a1vyL6nZMmfXZcZ/a9g+r7uoFzoE2wxsaCpmlYvgep/X+a1iQgE2+LwjB+lquYFH+Ztam5HOneY7wNMD8EpLbyUq6RO54+f/1COipeLZxyUc8tI1zMlsWlBKNSl0qljZqA3HAE3a49xQBPTiEHlfsLk9me55zkR2t/H9+HBlJ5LQvGb43sYhVssSfv7mYTAzEOJPiAjUiLfpg47cu+dt+c5x4qenFmqUDh78J + UTILS_SECRET: AgCAxEUaZS1TqU9HA2a0Ze1lxsP97DYgyUnWFaVAWc2qSM3sqp4BffN9ZJ8mVUXkZdZXmY5Yoirui/p4z/Ie1BpZBH5dbcuVGWu1DopCcs0UROpftJTWLKLZjoYGxp6RbazUNSVc500KvLuwMX+E0wG7OUSAt+nRN65/ebHRWl4kg1DceFLVQVmies/uHHz1++1jkVTGrr2ClLdmj0z/VYc5dYifxouYUEgjKWzB9ERkso/J5CCjG7hRRTbAeEaRH/SeyV6qw9kvhgCK8Kd0wicJatu8EcbHBDZs+0IiQ6vkqkdQE47MlFqfwem/lLWM9ce9rGBMkJ/rcMTGgW+pP6GKaQVKrhP3+0G6lz7IHneFvEF+W5tEaRjf7VbQdbgoX4lfykjPQ8pk3cIGPWLOrFQTxdl5EOhwS3dQ/WoAQs/GLgDsNUO8TlQziyURGCJT/LIfXvEVZsbOTcrbayd3ywwkL5aGN0TAcnWb0TPXgZVhw91N7Z7e2gRoGx2CoSpZzjv0k3liBrffCRYvvc85HrQnKtd8l0uutpCrtVOStc1wqRA5g0s9j0bgqspvQp7CGfyxs7vnGP6PMBoOtgH8mR14oI41SXTUn30uRWzGR3yebGLRXZ6ORgtzB8lQxDZAo6VUcc4afzyL8RXdhtJNmr/goRXnMg8LQqq80teQxFat9RW4Wxjqpnhb2xNwFtOxYFK818q+L7/7CC9MqaQcnYwn+mJUlMQTam8rPI1v1VRLfdzXcmHgQCXrlaTAMFOBF5zPO2vr3+OES/XS8r4K9V4f + template: + metadata: + creationTimestamp: null + name: outline-app-secrets + namespace: outline diff --git a/apps/outline/namespace.yaml b/apps/outline/namespace.yaml new file mode 100644 index 0000000..0769c05 --- /dev/null +++ b/apps/outline/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: outline + labels: + name: outline diff --git a/apps/outline/oidc-sealed.yaml b/apps/outline/oidc-sealed.yaml new file mode 100644 index 0000000..7be377c --- /dev/null +++ b/apps/outline/oidc-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: outline-oidc + namespace: outline +spec: + encryptedData: + client-id: AgAvHl9m4WXOYPIFG7rrYzPeA0Ksm2DUs2DS9gJismHLL5fX63e2KaD66W18NIFZQvtkfe8QelvBi3J2YAH2I1qrdZjn0SVJSq/7cdR+0X6lb71/hAkbHUr2xS+uOV86/O+6/htLhdNl0IcAjvyNMXWh5ptVBW/pbTQHJqGYN1dHHDQKri/3XIpxipKIXL22oPnowv0jcW1tgTZPPFkFOpahVlVP9n0pxM/2X3bb/bbsNVuTk0tWeDTqj0XTUN/0Kowq8P4rJFVdbJPyyV/37PnPlu3+F1i/flApo3HKQmoKR09ogCS6uZEP10SO7lmz0+JCMCIxYAHr/ELaynEXWhsCNJsLTE+TtMdMNciy7n8TZ75cS+Azgjhc5cimreimmxuFI1NRuMHnLMCLam1Hve2190sPhcIT+O5Revrc+BKsSXuscDZtrUugOChqYM9EW0T3AwmbIAyBQsY3MoLc2wFuq1fpYMx+Yu/0ajPBZvUV2JRbVt5YtMHoo3VbtKXkbfZW9IaBGcBFDxg7Dd8cZju4ljieRWJm6hISO6OPrMjvGG1IvBTv9c4SEGhnCHbjpYeoB9UPlL/FKVQgyrK1v5qhnZ0ZoiKYecxfj6Jytrf/qLdrBZDlCHghKpn3OM2OeubOak+xH1zgENK1UhjhkuqELoBXy9WDTVkOvrm5Tiw4WAAUm9jmjQiKusdwUGMPBt49sXOfyzsu8iPWIb8FDhH3DLF13d9wH7vtwlGQBuxhC1+FiSI= + client-secret: AgAHp4jsX5igXGHwKiQJuOKgyIIOssntobUhHnIKtSrNDK5mBZ0EvvwijvF9bqOp2i542WxVcjabk9a06/h6QCXymmo9Lcq4kK3cZyrQ6V7efjNFhogHOnE/f+y+lZy3AawTd1uokLiURf9z2X9rxi+2ppLhqExNFa7W3WIuEoVnk55H6nMMOxBK3SFfXi7PQUt+gJWGdZRMltVnySjqnqVbHwsO73uNoHLrOI4pEUeQiPaSQngkQRGa5LAYm/A6jYGYkTFQtvT2gee+aLTITzynzwdUmCdrJ0GcL/bi122dI3f+Kpkdj61Z2G2VZY2r2blXtQZcm2vt2j+4ZRWwMejWtpnpfBAnFm2wThvFiXqIYit8AtXu6isuj2bmwqtgZT5ieQeop7ieu1LBuqCutNkn+coxdmQWH+/Ct5oYl7GTHsPWqGlXF9Irel9J05SxZGmCtjw7sdXeGXwRItP7/eh6attCJZJJaPCD5/1/cNSVoLrifBQnNV/NoxWYzXsrYpz6LdNq38kK8eEB/YtNWMoCLN/NUtY2SbrJBlQChdWNeovxz9D3ddujFb+792iQ7qHcm2DGAEX2fKqeYTFY1J1Rp/eDF89UMocjJ5pgvfdjqYNuGXdARYxjBJybMskS+MhNd87JkjNAyRDDhU7hIe09Q0KtR9eeoe8wQ+8R2oOXoivAsm/KmyNGDTKPM5xUCvDABHjaaMmHczSAAdZcc/dYnrdgMGPcONeyVkhPgr90Fw== + template: + metadata: + creationTimestamp: null + name: outline-oidc + namespace: outline diff --git a/apps/outline/outline.yaml b/apps/outline/outline.yaml new file mode 100644 index 0000000..25937d5 --- /dev/null +++ b/apps/outline/outline.yaml @@ -0,0 +1,255 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: outline-data + namespace: outline +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: outline + namespace: outline + labels: + app: outline +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: outline + template: + metadata: + labels: + app: outline + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h outline-postgres -p 5432 -U "$POSTGRES_USER"; do + echo "Waiting for outline-postgres..."; sleep 2; + done + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: outline-postgres-credentials + key: POSTGRES_USER + - name: wait-for-redis + image: redis:7-alpine + command: + - sh + - -c + - | + until redis-cli -h outline-redis -p 6379 ping | grep -q PONG; do + echo "Waiting for outline-redis..."; sleep 2; + done + containers: + - name: outline + image: docker.io/outlinewiki/outline:1.10.0 + imagePullPolicy: IfNotPresent + ports: + - name: http + containerPort: 3000 + env: + - name: NODE_ENV + value: "production" + - name: PORT + value: "3000" + - name: URL + value: "https://outline.basicstack.de" + - name: FORCE_HTTPS + value: "false" + - name: ENABLE_UPDATES + value: "false" + - name: WEB_CONCURRENCY + value: "2" + - name: DEFAULT_LANGUAGE + value: "en_US" + - name: RATE_LIMITER_ENABLED + value: "true" + - name: LOG_LEVEL + value: "info" + - name: FILE_STORAGE + value: "s3" + - name: FILE_STORAGE_UPLOAD_MAX_SIZE + value: "262144000" + - name: PGSSLMODE + value: "disable" + - name: DATABASE_CONNECTION_POOL_MIN + value: "5" + - name: DATABASE_CONNECTION_POOL_MAX + value: "20" + # Secrets + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: outline-app-secrets + key: SECRET_KEY + - name: UTILS_SECRET + valueFrom: + secretKeyRef: + name: outline-app-secrets + key: UTILS_SECRET + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: outline-app-secrets + key: DATABASE_URL + - name: REDIS_URL + valueFrom: + secretKeyRef: + name: outline-app-secrets + key: REDIS_URL + # S3 (Hetzner Object Storage) + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: outline-s3 + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: outline-s3 + key: secret-key + - name: AWS_REGION + valueFrom: + secretKeyRef: + name: outline-s3 + key: region + - name: AWS_S3_UPLOAD_BUCKET_URL + valueFrom: + secretKeyRef: + name: outline-s3 + key: endpoint + - name: AWS_S3_UPLOAD_BUCKET_NAME + valueFrom: + secretKeyRef: + name: outline-s3 + key: bucket + - name: AWS_S3_FORCE_PATH_STYLE + value: "true" + - name: AWS_S3_ACL + value: "private" + # OIDC (Pocket ID) + - name: OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: outline-oidc + key: client-id + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: outline-oidc + key: client-secret + - name: OIDC_AUTH_URI + value: "https://auth.basicstack.de/authorize" + - name: OIDC_TOKEN_URI + value: "https://auth.basicstack.de/api/oidc/token" + - name: OIDC_USERINFO_URI + value: "https://auth.basicstack.de/api/oidc/userinfo" + - name: OIDC_LOGOUT_URI + value: "https://auth.basicstack.de/api/oidc/end-session" + - name: OIDC_USERNAME_CLAIM + value: "email" + - name: OIDC_DISPLAY_NAME + value: "Pocket ID" + - name: OIDC_SCOPES + value: "openid profile email" + - name: OIDC_DISABLE_REDIRECT + value: "false" + # Persist local cache directory + volumeMounts: + - name: outline-data + mountPath: /var/lib/outline/data + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + livenessProbe: + httpGet: + path: /_health + port: 3000 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 5 + readinessProbe: + httpGet: + path: /_health + port: 3000 + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + volumes: + - name: outline-data + persistentVolumeClaim: + claimName: outline-data +--- +apiVersion: v1 +kind: Service +metadata: + name: outline + namespace: outline + labels: + app: outline +spec: + type: ClusterIP + selector: + app: outline + ports: + - name: http + port: 80 + targetPort: 3000 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: outline + namespace: outline + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: traefik + tls: + - hosts: + - outline.basicstack.de + secretName: outline-tls + rules: + - host: outline.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: outline + port: + number: 80 diff --git a/apps/outline/postgres-credentials-sealed.yaml b/apps/outline/postgres-credentials-sealed.yaml new file mode 100644 index 0000000..e869e49 --- /dev/null +++ b/apps/outline/postgres-credentials-sealed.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: outline-postgres-credentials + namespace: outline +spec: + encryptedData: + POSTGRES_DB: AgCbJr6ng8tQwTMyNfrvubnlA5CvamfSZtzR4AwuIn/UUcK6mb68B5ucCUyA0GeeFVOGx63rlibDCKb42Da0AHrSYDTzZ1YbYAiwEzdlrmgh5tCfMiqbcUHUqSR4L7QAjkS50ASQXPXxSHuxuYa9cO7EQCasN7Fm5cmTkbcGHTKOSJ7fE7VQDBXyf6Z4Rga8HBys20xjglDFy7meBLEXDOKRUkQDC/PGgqlF98ZVtMH148ziaRxiu2tkbpsFchQ/zFc6x5rtllYGnlic8ssbgAXwUL6WZO6CcSbjhSkq4JcIOJh60GudsthTaW5HTldrD0VwaLXw6BNSD9+7+Zd1UIKSN9+imBOCmpOglLG2LFCxUD02lr3sPBdlztQwZnvxvFhfKRXstn/Qo8ujzKlRFyHdLsX0IQf7vj9kb+Giq54MAgelqivl7HMg8O73DIHe/GUmwDr3cXt1+aVoPKnQHe5wkQ4bIsTDzkNZzig1Lisl/bBxF2tf48ZXP5QZXpcL80TX6ZLxh0u1MVTpmIT6fZziw8lOJfLdE917UokNPnFVWx9X7Z+zWh07b/AMIx1Wa5JNKl1rpmJazYY42pvEGU2crR0NETNYavnM95Pl9sbRh1f/grp3v8Y4J31Oyvy2+MMChe1FdUbOnMN3Aozc3OLFuu94aiJFd1r2YU/1VCSa/0VuaDcCPwtFvKf92B/ADh0HT0Lxe7VW + POSTGRES_PASSWORD: AgAGj5wpTMEcDHT20f1SKTd+J0ZSC6bhfBP2WOSa3zBbxqI7Rb0OEuR+tsuQJyEbL8uKPrKKvgcjf4/GGTNChpkoe/n9Z5Vsc5cvdel59egtIca3R/2g+EhUqGhrb/R4cVMylqANcpK3O25GJNL6K9TfVU03qlORazVVp2qNr+cQ/fWUHqAJUz77ui6KPjGTjsrKH4Vnh5eP40ZoYRDXOmM7AACgMfw4dtaUID/QhslaGE8vi0o/sv/MyhC0Pic98V/cwmwPgQCYgH44v56EtOR7X0Rqz08so3GGi4hvrQCs1i40Sd2k0SayBNmvF0LGO9stlkDIpmMmnsEeCgxk0u5L9tRxsDtvGX4AAiU9FByWhVp4JRYd4QAF509p3aF+1/3kNQen+4aHefy4Xzpx1dDQOZoWArz/kXhyh8u+wkkDwsf/6VXSORdd2w4M2lDyhP61pGY8FC0QOCKWNkfOTTgFOJf8guqwSfmLNdS+lhJmtpgKpNwPnG90CgV8b6xd3uPXYa6FQPd7I9sFpN4I8ZC9SJZE4karU7Fys/SArdBB2proemw6SYepBvo41bcdftxYpL5RvfCkFjLj6CDTYbQtjPzV0YeG03l1qKqbfiKroC242mGprJweGdC4kM4L28aWaMq0oGg6og7u0A84YIukQlncIMAQ3t4L78Zp/OCOWbkPkWykjUVCkOtxMSnIjfWvFVN8vr+zN0AD/PwPu976D1RdCWXzXyZu1gWZmnBEUQweYw1mFMsaqAxQl000UWc= + POSTGRES_USER: AgAqZajhuIMyXzZtwm3C6KCN6njShH2tdlj4U8IsZxc3v0rHdtlCDfjk4db/fkrmRZXGzJmjizVfMwlBjVAXlO7WiGpZ0eDG/UQMIR7f56UD40Ccj2rJOExw0tB/mnfCmXVVMsUyeA3IPJLY+lUB40IE6nw7K3n8A5XpyaGTCPa8QFlCHvwpQ/seDNykA4sc+NX4So5sPUS3h/NFo9T1XhI6prvDWOd5d0YEFYBoHSgQU/QcIizT6sgudVl4cKtWtRrJdRSaq6msREB4hFfc4DnaxGsB3fLmsfUC881nkSc25ayGvnJXvWhFCnIy5v15cHFT/Yo8yHqVQdWVgJWKxb8fZfLGtXuRGXYoLHbH+VGhAcPUDSERmBoyCKG4ABGWkXG6+xLG7meN9q2/u0zbmCoYZRSxflJIUOWaZIGuvJq6KmpKsEJ5A58M90Y308oSS7yHyX3pgGJVV3zWZC6ddOoPPGbpipxAESUq7XCJnTth9SkIttlGBIgiFZ0wErL31LileoEGvhMcv5H5MSzebZbfW+uJIMRJF5QfaL8BS7UCmdI9j73VxT7pxupxKeuQCJ/zsF3B13IR/+w1hEwNW0TugqRlaqF8agU0jCLF0tlD5aCnrqHJp7MB/zD3Lb9Pk30mUEeye++kx4kqTE2Vf5sPAHbIjBlOvm/y1lKxDjASoab+I6mp4/fTBe4+S+Unqw+R7uWxBRUb + template: + metadata: + creationTimestamp: null + name: outline-postgres-credentials + namespace: outline diff --git a/apps/outline/postgres.yaml b/apps/outline/postgres.yaml new file mode 100644 index 0000000..a37d327 --- /dev/null +++ b/apps/outline/postgres.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: outline-postgres + namespace: outline + labels: + app: outline-postgres +spec: + type: ClusterIP + selector: + app: outline-postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + protocol: TCP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: outline-postgres + namespace: outline + labels: + app: outline-postgres +spec: + serviceName: outline-postgres + replicas: 1 + selector: + matchLabels: + app: outline-postgres + template: + metadata: + labels: + app: outline-postgres + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: postgres + image: postgres:16-alpine + imagePullPolicy: IfNotPresent + ports: + - name: postgres + containerPort: 5432 + protocol: TCP + envFrom: + - secretRef: + name: outline-postgres-credentials + env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 1Gi + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + subPath: pgdata + volumeClaimTemplates: + - metadata: + name: postgres-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/outline/redis.yaml b/apps/outline/redis.yaml new file mode 100644 index 0000000..944dca2 --- /dev/null +++ b/apps/outline/redis.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: outline-redis + namespace: outline + labels: + app: outline-redis +spec: + type: ClusterIP + selector: + app: outline-redis + ports: + - name: redis + port: 6379 + targetPort: 6379 + protocol: TCP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: outline-redis + namespace: outline + labels: + app: outline-redis +spec: + serviceName: outline-redis + replicas: 1 + selector: + matchLabels: + app: outline-redis + template: + metadata: + labels: + app: outline-redis + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: redis + image: redis:7-alpine + imagePullPolicy: IfNotPresent + args: + - --appendonly + - "yes" + - --save + - "60" + - "1000" + ports: + - name: redis + containerPort: 6379 + protocol: TCP + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + readinessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + exec: + command: ["redis-cli", "ping"] + initialDelaySeconds: 15 + periodSeconds: 30 + volumeMounts: + - name: redis-data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: redis-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 2Gi diff --git a/apps/outline/s3-sealed.yaml b/apps/outline/s3-sealed.yaml new file mode 100644 index 0000000..92c4ded --- /dev/null +++ b/apps/outline/s3-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: outline-s3 + namespace: outline +spec: + encryptedData: + access-key: AgCMbJDgvGRG0ritkltoJF/nFpb7p4JRbl57Tje3EUx/+jCZDqhvM2J6DAxLL4ELy6xINuk7bByMU4Ry5z6lmYTyZ6UvgYpJIkrg9xS7dA4WU2474oGbDWx/u2xRvI0M3t/JqlfkShaX/jxDVSibYGpGvN/+uZ/F308EvUOai/PLgEz2VpA/6y/5gtFdrDjDFoBLs5eZsrBPDAIweKQ0s+5wiPlHv+vDhIvw99efS4wiZZQXuD7j7zKJIxwGKG0k6VGIoRm207xxvHoSXmxmhZp0I6whKJ4XJqXo/S1nzQllQWOaaUyA2Y+sGXfjbcm1YdNN48/MZK6uWJNjXaIphmW6DehW8+i3aJLbv/XSmbxcY9fowRyEfFz3aqqtGW7gP4CzUc+0DwQhfE+9z/lBoWOTKsDBf8kUYnF5GPCMB1TjAAP12MWCsBmecP9kppMawH2SmRZbl+ImZgiQTXlJ79gNRoF2ri4TF2xz/tcjP3Rnt82p8yV3gj/NDsIMLUG1GPiNDMdz8dNR723V9pb+lIoNUD+pb7+Jr1N2wCq21YKNlUGPC6ZXK5flQVwlKXbxNSyu1KEyhzgEnRjKBXTQOBlRcUkkaXNbA4FreuyvfCJcRkScf3YsfzJQcxxiCxK+A5UDd9sgiVwi3rYYfQhwzPUDcvjVMTX93qZaKPfmfcxg0STV5M+A5pAkSVvs/8ORS0HoPqvXZRlH/fOT040n4z1b4ALnBg== + bucket: AgAiu0w47e6zbhFOX7dOGgDJVRAy5ZDfWmjgYGMibCCFm+FglEmKaj5Vi+70nZMsUigtH98WKKfEdSsJ4+mGuITNSxU2+p0uth2D2JapkEmOtzbzMzpagoXLnRV2xZW/HKyiUhRct7ygawUQ6TA3A4JBb3G/6WK+T1AxF2Z0NtDTULmM+mx8a4XNE1Anmuo6zOUQ2GIUfbBOI6RCBP693fVI8m+eB4bzkbmwrTXdT1bHPQ95wWbX28wDI3i9PQwRlM2oM5OYOTn46l3V+5mwPTUntuGuMis5P1jH4O7EkUObCbeVsC4S4orZ/sYPBs580MXFzBGeKM/Ce1fGVICC6kMWqt1SFnlYvEGRej1qj8N6uS5lmLNG6HM6Z3nHSiM/6Pi3LzfvnEvum8x6qmqRGsergIHTAcaHBOOiVDrjRKRqlCTRDmOjoTbB0vD00cWXAFGTjTXKsFtrcAmzq3EF3vhrA69Vf2FeJsF4m+ixlFdZd7WqrL5H2W7tDEHtbMhMHiii5cfHkKD9orLFpfMzirSR+sLdeRzR5t5pvGIkPRVECEHU+8w9vWSxm2aSFXOJUH4nOMM0TNzlJNlD/vsDL+Z/JQjQDzBKyBIHp1hKo45JxjwgmZKjsDy4Ts8CEwPp/zPwSFY96hqFVVs14PqjWb17y9oCpca181rQ+7bmrW0F4OCXTGg4wRBxk0ikDB7JbwCzZ1d7VqNB5lOfzeDPgZq8d98= + endpoint: AgB6zhkvtiqfkoSMExAy5Imd9LrBkC04F7Ifx/YSGfGtcjFSNsACNNL2vnVKUyKWvuDPa+ptdJh6HLUvmk5D9myMxlH8Bc7p69x4/wUqvwRiBgwm/4Gge3XlYa16KzMJIhkaVu6UxNSEI3c8S94Rvv7uiCk+IDkROUnutVC3dzRE6OiFz5GL56AO1f7bOj7t+0Rdpq7rSDoBq1e1Wftm6xnHa56BGXGlpaH/7UU0h0MDlnAyAbE4Xi04FLk3s7gjPPA4tetbtEBEBbKTA7GokWpt4rXrqivLr4hKd/OSklI9SByd9SxpdbO2m6QrunIqQDjjHkDrPIOAelM1MOVSTJKG1JWfXqywA2l/WeqIGEQt8FW4wWCU0E4XKixsJdFEDjtFAOyVJPYoyObaVUvkXrZdxPuc+KFtocSXR9KM0CaPQ7jteqOfYI+4VDwvE/YRVHJrolUzqBpu/pKkHnDDottfJ/O7OJCGl+jfQ1ZzP4ovOVH+GIBmo3rjVWZM+f8KlA8HA6NAdAWuSE8z3phP7n8N2ZzICr5KsW0vNSI5Ue7/mvjCbm9nUushAuzvFX7s4YH1KtwQrToMJTylfUdxyBjLzJ2NOOsg7ZiaWw9j9tjrAv1SRhPps/i7cy2Fg6Z1qeX+mvWgeIjKaQvTdYH0HREUXpzAMe1UWUkCJQtCrk8nk7gigmxF3T/fO0AIxMQGknRKYdqp3Hr06M09P1WF5QjrLU7uydWbFhA6Js5DWd3d2MurpQ== + region: AgBFM4EAaxptRlqN8EnsrmeX950mS10T7ZlfZmdpOsQJSR1Z0CzN5gmsJmVlznrRqw1FGGXU3feBrg/eA01IP5ykqq06iLWyjla3oLVWaYzwK/wASiY7Ycl2tkuyZwpOnQNWuxmXFX2WHl0NNKbd+mklmBxaIPRQVdYqtDAUeblyTII8oBFmv1zc4kpf4MCCHhsCG7m4VG+b+EI/DZZRXK5LFaVoOFy1pHGhN8um/OerDICHuOleOQ+hWj3w5mkigdKzFZkeIG9RQ0WnvS1muwECKvanDBaS0RGDxt+Ipm09WPk4VyGAzHyDd5wOd/uckD/yYFzAodjlZHDQipgNE1MAs43gqt6X3lKNAwqOEc6TLmQv6FUTU9+R7J+2FOgSKQ/ACTVyGHiebOqjb2L9bk1oj/j0cKARRQyRyL8B/TFHPVfyPH+kiTmRa+qlsg7FN7oTeFSOWaNZbA0jz+ONM9IzRpE33VHS/r9cCmfe2rFySNXCjHIkiKJC9qMYBqorM1kM4Ab00hlfhdO0aspTW9KC0pEauPs0n84sl/HHbOQ+sLcYW7QBvZvuRi1OyVM99XoKjI7Jnu7Aq55NX+rt9zZ+iJpnXeUm8hxCKC/CyUS5mAEmqQpJoKqLVMfhvpXMn8I5lXs3YpshHto649EPTVS6A23YBG4Q3toZ8UmbXp2JtxlL+JnfU0/mZvAPOBkfrmx5MToa + secret-key: AgDCSueSKDzmfI/5zhEusMX6AaIpjF526UdnWdTnmUfsX+LW+w/uQvgM0aPIoR/sMyEQoo/UxouU3V1PQIHj9Q/3acDuFBkXCFFb02dFv+gXF3SAEGnCaHviH8Bce12SfMePyOlKT2eDI8vTyehw3istEMTJHc7rMr/PNZprKG1T35wxdaklS88uLP5oAnHVKeqEvAvgNFXWrMqJ1GM1l8/rTd5GCJ8xsE1mGU/26Ac518nzo2PMARZYwZ0Rfiv2+QZswT7DDQNH6ZoY0DPzL+SW9ecMGc933L6EejNzco1esttEtRa+ZJRZ2tqUbm8LTZOFHq500VRtbEAcAtySMtjxX0VF4sSGrxGGooEkY9mqOcy2yQLZdb9UGObF8wMBXcGx67SAcXbppWeNSOvp4p7QVrzVbNXIbnV+7e8XrKBHf9inBW2j9oCgPjPcTKW7VfHOQKIYKwnHS81hgOsTQcX+BcYT9Bkea4E+/R0ovYJh68bd3Wwcm/dECnj3y3Kzgj2j4/xv7aO4cNacAt1INILYY9P0edYHbCnO4gHXt9itvKTbiOslYRuxfgff490ReXunkTl/FFawmwCZoVY7nlp59IKwTB/PahPctGmrbHstwI6UK5kLaB93Cf6bNengLCt+GYAmuuiTAOqfTLvGRQBu074mqWAO9ThYkdU0ebtOO9sY8bALk2ZlSdEN0T5Aqmp+LaSz6W91nLDF8/XaGD6zIpHHjwF6oCPg6BPn//43Rx/unbjJf82j + template: + metadata: + creationTimestamp: null + name: outline-s3 + namespace: outline diff --git a/apps/pangolin/.gitkeep b/apps/pangolin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/pangolin/README.md b/apps/pangolin/README.md new file mode 100644 index 0000000..0d9796c --- /dev/null +++ b/apps/pangolin/README.md @@ -0,0 +1,240 @@ +# Pangolin + +Pangolin is BasicStack's self-hosted reverse proxy and SSO gateway. It sits in front of +selected cluster services (e.g. `paperclip.basicstack.de`) and forces every request through +Pocket-ID before it reaches the backend. Pangolin does not terminate TLS or handle the +raw HTTP request path itself — it publishes a Traefik dynamic config, and the +`pangolin-kube-controller` translates that config into Traefik CRDs that our existing +Traefik ingress controller enforces. + +Public URL: — App source: + +## Architecture + +``` +Browser ──HTTPS──► Traefik (ns=kube-system) + │ + │ IngressRoute + Middleware (badger) + TraefikService + │ ▲ managed by pangolin-kube-controller (ns=pangolin) + │ │ + │ │ polls /api/v1/traefik-config every ~15s + │ │ + ▼ │ + Pangolin API (ns=pangolin) + ├─ port 3000 external dashboard API (session auth, behind Ingress) + ├─ port 3001 internal traefik-config API (Bearer auth, cluster-internal only) + └─ port 3002 UI HTTP (behind Ingress) + │ + ├── PostgreSQL (ns=pangolin, pangolin-postgres) + └── OIDC provider: Pocket-ID at https://auth.basicstack.de + +Auth flow for a protected resource (e.g. paperclip.basicstack.de): + Browser ──► Traefik ──► badger middleware ──callback──► Pangolin API (3001) + │ + ▼ + Pocket-ID / session +``` + +Two long-running workloads live in the `pangolin` namespace: + +- **Pangolin** — the app itself: dashboard, resource/target CRUD, OIDC handshake, + session cookies, and the internal `/api/v1/traefik-config` endpoint that + publishes the current desired Traefik state. +- **pangolin-kube-controller** — a Kubernetes controller (leader-elected) that + polls the Pangolin API, then upserts Traefik `IngressRoute`, `Middleware`, + `TraefikService`, etc. via server-side apply into the `pangolin` namespace. + +The controller only writes CRDs into `pangolin`; the backing Service typically +lives in the target app's own namespace. Traefik is started with +`--providers.kubernetescrd.allowCrossNamespace=true` so those cross-namespace +refs resolve. + +## Components (files in this directory) + +| File | Purpose | +| --- | --- | +| `namespace.yaml` | Creates the `pangolin` namespace. | +| `pangolin-config.yaml` | Pangolin `config.yml` template (ConfigMap). `__DATABASE_URL__` is rendered at startup by the `render-config` init container from the sealed secret. | +| `pangolin-deployment.yaml` | Pangolin app Deployment, Service (ports 3000/3001/3002), Ingress, and the `pangolin-data` PVC. | +| `pangolin-secrets-sealed.yaml` | Sealed OIDC client id/secret and local admin password. | +| `pangolin-postgres-secrets-sealed.yaml` | Sealed Postgres credentials plus the rendered `DATABASE_URL`. | +| `postgres.yaml` | Single-replica Postgres 17.5 + `pangolin-postgres-data` PVC. | +| `pangolin-controller-config.yaml` | `pangolin-kube-controller` env: `CONFIG_ENDPOINT`, target namespace, log level. | +| `pangolin-controller-deployment.yaml` | Controller Deployment (image `fosrl/pangolin-kube-controller:0.1.0-alpha.1`). | +| `pangolin-controller-rbac.yaml` | ServiceAccount + ClusterRole/Binding: CRUD on Traefik CRDs and leases. | +| `pangolin-controller-service.yaml` | ClusterIP for controller metrics. | +| `pangolin-controller-api-key-sealed.yaml` | Sealed `Authorization` header the controller sends to the internal API. | + +ArgoCD Application: [`apps/app-pangolin.yaml`](../app-pangolin.yaml) — sync policy is +`automated: { prune: true, selfHeal: true }`. Any local edit must land in git. + +## SSO integration (Pocket-ID) + +Pangolin authenticates users against Pocket-ID via OIDC. Two independent SSO +flows exist and should not be confused: + +1. **Logging into the Pangolin dashboard.** Configured in + `pangolin-config.yaml` (`oidc.*`) and via env in `pangolin-deployment.yaml`: + - Issuer: `https://auth.basicstack.de` + - Callback URL: `https://pangolin.basicstack.de/auth/callback` + - Scopes: `openid profile email groups` + - Client id/secret: `pangolin-secrets` sealed secret keys + `oidc-client-id` / `oidc-client-secret`. +2. **Protecting a downstream resource (e.g. paperclip.basicstack.de).** + Handled by the badger Traefik middleware that the controller emits. + The middleware calls back into Pangolin's internal API (port 3001) to + check the session cookie, and if unauthenticated redirects the browser + through Pangolin's own OIDC flow. There is nothing to configure per-resource + in Kubernetes — the resource's auth policy is set inside the Pangolin UI. + +The Pocket-ID client is provisioned separately (see [pocket-id docs](https://pocket-id.org/docs/client-examples/pangolin)). +Do not reconfigure the OIDC client from the Pangolin side once it is working. + +## Ingress protection workflow + +The end-to-end flow for a protected hostname like `paperclip.basicstack.de`: + +1. Operator creates a **Resource** in the Pangolin UI (hostname, backend + Service, port, auth policy). +2. Pangolin persists it in Postgres and exposes the new desired state at + `http://pangolin.pangolin.svc.cluster.local:3001/api/v1/traefik-config`. +3. `pangolin-kube-controller` polls that endpoint on a ~15s loop. +4. On change, the controller server-side-applies into `ns=pangolin`: + - `IngressRoute` for the hostname (with `tls.certResolver: letsencrypt`). + - `Middleware/badger` (session check + OIDC redirect). + - `TraefikService/1--service` pointing at the backend + Service in the app's namespace, port from the DB. +5. Traefik picks the new CRDs up and starts serving the route. +6. First request → redirected to Pocket-ID → session cookie → subsequent + requests are proxied to the backend Service. + +## Adding a new protected ingress + +Prereqs the target app must already satisfy: + +- A Kubernetes `Service` in its own namespace, listening on a known port. +- A DNS record for the hostname pointing at the cluster ingress IP. +- No competing plain `Ingress` in another namespace for the same hostname. + If one exists (e.g. a legacy direct ingress), delete it first — Traefik + will otherwise route the pre-existing ingress and Pangolin's IngressRoute + never wins. + +Steps: + +1. **Log in** to (SSO via Pocket-ID, or + local admin from the `pangolin-secrets` sealed secret). +2. **Create the Resource.** + - Type: HTTP/HTTPS. + - Domain: the exact FQDN (e.g. `newapp.basicstack.de`). + - Base domain: `basicstack.de`. +3. **Target.** + - Target type: HTTP. + - Backend URL: `http://..svc.cluster.local:` + — the port here is written to the `targets.port` column in Pangolin's + Postgres and used by the controller when it generates the `TraefikService`. + If the Service port later changes, update the target port in the UI (or + via SQL: `UPDATE targets SET port= WHERE "targetId"=;`). + - Path match: `/`, strip prefix off. +4. **TLS.** Enable TLS, force HTTPS redirect. The Pangolin-generated IngressRoute + hardcodes `tls.certResolver: letsencrypt`, which is provisioned on our Traefik + install (HTTP-01, persistent `/data` PVC). Do not disable that resolver in the + Traefik `HelmChartConfig` without also removing every Pangolin-generated route + — Traefik will otherwise fail with `nonexistent certificate resolver`. +5. **Auth.** Enable authentication, method SSO/OIDC. In Community Edition (what we + run) there are no shared policies or group rules — add authorised users + individually by email. If you need group-based rules, that's an Enterprise + feature. +6. **Save** — controller applies CRDs within a poll interval (~15s). +7. **Verify.** + + ```bash + kubectl -n pangolin get ingressroute,middleware,traefikservice \ + | grep -i + kubectl -n pangolin logs -l app=pangolin-controller --tail=50 + # HTTP smoke test + curl -sSI https:/// # expect 302 to Pocket-ID + ``` + +## Troubleshooting + +- **badger middleware returns HTTP 500.** The middleware is trying to call + `apiBaseUrl` and either the URL is not an FQDN or Pangolin isn't reachable + from `kube-system`. Confirm `pangolin-config.yaml` sets + `server.internal_hostname` and `server.badger_override` to + `pangolin.pangolin.svc.cluster.local` — the short name `pangolin` does not + resolve from Traefik's namespace. +- **Controller logs `conflict with "controller" using traefik.io/v1alpha1: .spec…`.** + A pre-existing CRD was created with the `Update` verb and the current + controller uses server-side `Apply`. Delete the offending + `Middleware/TraefikService/IngressRoute` once — the controller recreates it + via SSA and the conflict is gone. +- **Traefik logs `service port not found: `.** The port stored in Pangolin's + `targets` table doesn't match the Kubernetes Service port. Fix in the UI, or: + `UPDATE targets SET port= WHERE "targetId"=;` (identifier case matters, + double-quote it). +- **Traefik logs `nonexistent certificate resolver: letsencrypt`.** The + `letsencrypt` resolver isn't loaded in Traefik. Every Pangolin IngressRoute + references it — either define the resolver in Traefik (see + `infrastructure/networking/traefik-helmchartconfig.yaml`) or override + `traefik.cert_resolver` in `pangolin-config.yaml`. Grep the Traefik startup + log for `unable to get ACME account`; that appears when the resolver was + skipped entirely. +- **Route doesn't resolve cross-namespace.** Traefik must run with + `--providers.kubernetescrd.allowCrossNamespace=true`. Pangolin's CRDs live in + `pangolin`; backend Services usually don't. +- **CRDs never appear after saving a Resource.** + ```bash + kubectl -n pangolin get pods -l app=pangolin-controller + kubectl -n pangolin logs -l app=pangolin-controller --tail=200 + kubectl -n pangolin get configmap pangolin-controller-config -o yaml + ``` + Common causes: controller CrashLoop, wrong `CONFIG_ENDPOINT`, missing auth + header sealed secret, RBAC missing after a controller image upgrade. +- **Login loop / redirects to Pocket-ID forever.** Clear cookies for both + `pangolin.basicstack.de` and `auth.basicstack.de`, confirm the OIDC callback + URL matches the Pocket-ID client, and check Pangolin logs for OIDC errors: + `kubectl -n pangolin logs -l app=pangolin`. +- **Pangolin pod stuck in `Init:0/2`.** The `wait-for-postgres` init container + is looping on `pg_isready`. Check `pangolin-postgres` health and the + `pangolin-postgres-secrets` connection details. + +## Critical notes — DO NOT DELETE + +Pangolin holds live user/account/config state. Losing a PVC here means +re-provisioning every protected resource, every target port, every user grant, +and re-doing the OIDC client trust from scratch — plus temporary outages on +every downstream site that Pangolin fronts. + +- **`pangolin-postgres-data` PVC (ns=pangolin, 10 Gi, `hcloud-volumes-encrypted`).** + Backs Postgres. Contains resources, targets, users, sessions, and everything + the controller reads. Annotated with + `argocd.argoproj.io/sync-options: Delete=false` — do not remove that + annotation, and do not delete the PVC as a "reset" step. Snapshot the volume + before any migration or destructive DB operation. +- **`pangolin-data` PVC (ns=pangolin, 10 Gi, `hcloud-volumes-encrypted`).** + Runtime files under `/app/config`. Also marked `Delete=false`. Do not delete. +- **Sealed secrets** (`pangolin-secrets`, `pangolin-postgres-secrets`, + `pangolin-controller-api-key`) — rotating them out of order will lock the + controller out of the internal API or take Pangolin off Postgres. Rotate one + secret at a time and verify the corresponding pod comes back before + proceeding. +- **`pangolin-config` ConfigMap.** Editing `server.internal_hostname` / + `server.badger_override` back to the default short hostname will break the + badger middleware cluster-wide (see Troubleshooting). +- **The Traefik `HelmChartConfig`** in `kube-system` + (`infrastructure/networking/traefik-helmchartconfig.yaml`) — Pangolin depends + on `allowCrossNamespace=true` and on the `letsencrypt` resolver being loaded + with a persistent `/data` PVC. Removing either silently breaks every + Pangolin-managed route. + +If access to Pangolin is broken, **restore from a snapshot first; do not +"reset" or re-provision** the admin/OIDC layer. That is the standing rule for +every stateful service in this cluster. + +## Related + +- History and gotchas: internal memory `pangolin-kube-integration` (from DEV-457). +- Configuration walkthrough for the `paperclip.basicstack.de` resource: + [`pangolin-paperclip-configuration-guide-community.md`](pangolin-paperclip-configuration-guide-community.md). +- Pangolin docs: +- pangolin-kube-controller: diff --git a/apps/pangolin/namespace.yaml b/apps/pangolin/namespace.yaml new file mode 100644 index 0000000..fdbeb64 --- /dev/null +++ b/apps/pangolin/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: pangolin diff --git a/apps/pangolin/pangolin-config.yaml b/apps/pangolin/pangolin-config.yaml new file mode 100644 index 0000000..5758f0e --- /dev/null +++ b/apps/pangolin/pangolin-config.yaml @@ -0,0 +1,48 @@ +--- +# Pangolin ConfigMap +# The `postgres.connection_string` placeholder is replaced at pod startup +# by the render-config init container using DATABASE_URL from the sealed +# secret. See pangolin-deployment.yaml. +apiVersion: v1 +kind: ConfigMap +metadata: + name: pangolin-config + namespace: pangolin +data: + config.yml.tmpl: | + gerbil: + start_port: 10000 + base_endpoint: "pangolin.basicstack.de" + + app: + dashboard_url: "https://pangolin.basicstack.de" + log_level: "info" + + domains: + domain1: + base_domain: "basicstack.de" + + server: + secret: "REPLACE_WITH_SECRET_FROM_SEALED_SECRET" + # DEV-457: Traefik runs in kube-system; the short hostname "pangolin" + # (Pangolin's default) doesn't resolve there. Point badger + internal + # callbacks at the FQDN so the badger middleware can call back to the + # Pangolin API. + internal_hostname: "pangolin.pangolin.svc.cluster.local" + badger_override: "http://pangolin.pangolin.svc.cluster.local:3001/api/v1" + cors: + origins: + - "https://pangolin.basicstack.de" + + flags: + signup: true + verification: false + + postgres: + connection_string: "__DATABASE_URL__" + + oidc: + enabled: true + issuer: "https://auth.basicstack.de" + callback_url: "https://pangolin.basicstack.de/auth/callback" + scopes: "openid profile email groups" diff --git a/apps/pangolin/pangolin-controller-api-key-sealed.yaml b/apps/pangolin/pangolin-controller-api-key-sealed.yaml new file mode 100644 index 0000000..16b7bd3 --- /dev/null +++ b/apps/pangolin/pangolin-controller-api-key-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: pangolin-controller-api-key + namespace: pangolin +spec: + encryptedData: + auth-header: AgAqHAbAjJG5d8mY1sNkVamnWsl/xdxjthiUWaEd7mXMMHAF9hr+Opy6cIH5BerrU9wtS73YA7be3Xjz8cGFy8LdQvldIBVYzOsA/VTlwMKDoet8bO5eoGEH91Gz7haBBWJvezmeNNxkH7bGlHPvly7+JY8whx9XJWY/hKb6uH2j4fnbSkkL68WobsxbZeJikvLFQrQIxlH0nRc2se4bM+LWJKukiSlWx00qeTGsROvVhxK5tgRMYO4xwPaiEktY9ORHPUYqAmVgdu85d0ygv94XiIFoPSChluVKI9AociynKVb4dQiBbHpKQSsUFBD7US1o1wmgFY8LZCvU9FdbB37VAG5Bxt0VDvOws650NGXLS8CbzidjaBtlpFiJYZRviF2GevIj8UqcmtYFGuvmDMGsGtXOmpkSMiZQyco4lLSWGCwyOYAwEaMNbzzgPbq7VfKJ9efm/rxK7X2t15FCl4jHpI/TC/LobDEBxvK4eELynS5HtcxmwTafYRlp0l1cfTI2yN4ZrsBVsgbCDTZXDjcMuabC6qEjbNrk158KbotkdKFS9mv7Hr3JyrM7PfDQvPtFHUpAzcPESIsElewcwrxGOyLyzYqRPo7wGj9lt4xkQB7OYzlzIP/IxAqzQkiJFv7VU8qCrMVd3VYLn9pbQ71Hv9g+CfuanO1er51Z5s3l9XMA2Zpnoy2uimPkA8svWuIKYcevSH1Gv92zj41jWTy712B9S6w8V7O19Qz6YPBwsezA0EfouDclO0f3Kt4wb0T0QJHHgTBoSjLmC/mFNmQ= + template: + metadata: + creationTimestamp: null + name: pangolin-controller-api-key + namespace: pangolin diff --git a/apps/pangolin/pangolin-controller-config.yaml b/apps/pangolin/pangolin-controller-config.yaml new file mode 100644 index 0000000..10bdd87 --- /dev/null +++ b/apps/pangolin/pangolin-controller-config.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: pangolin-controller-config + namespace: pangolin +data: + # The Traefik dynamic config endpoint lives on Pangolin's internal API port (3001), + # not the external dashboard API (3000). The external port serves the session-authenticated + # dashboard router which returns 401 for Bearer requests to /api/v1/traefik-config. + CONFIG_ENDPOINT: "http://pangolin.pangolin.svc.cluster.local:3001/api/v1/traefik-config" + CONFIG_ALLOW_INSECURE_HTTP: "true" + TARGET_NAMESPACE: "pangolin" + ENABLE_LEADER_ELECTION: "true" + LOG_LEVEL: "info" diff --git a/apps/pangolin/pangolin-controller-deployment.yaml b/apps/pangolin/pangolin-controller-deployment.yaml new file mode 100644 index 0000000..7aa8683 --- /dev/null +++ b/apps/pangolin/pangolin-controller-deployment.yaml @@ -0,0 +1,76 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pangolin-controller + namespace: pangolin + labels: + app: pangolin-controller + component: controller +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: pangolin-controller + component: controller + template: + metadata: + labels: + app: pangolin-controller + component: controller + spec: + serviceAccountName: pangolin-controller + containers: + - name: controller + image: fosrl/pangolin-kube-controller:0.1.0-alpha.1 + imagePullPolicy: IfNotPresent + envFrom: + - configMapRef: + name: pangolin-controller-config + env: + - name: CONFIG_AUTH_HEADER + valueFrom: + secretKeyRef: + name: pangolin-controller-api-key + key: auth-header + ports: + - name: metrics + containerPort: 9090 + protocol: TCP + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + livenessProbe: + httpGet: + path: /healthz + port: 9090 + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /readyz + port: 9090 + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: + - ALL + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault diff --git a/apps/pangolin/pangolin-controller-rbac.yaml b/apps/pangolin/pangolin-controller-rbac.yaml new file mode 100644 index 0000000..4f17c58 --- /dev/null +++ b/apps/pangolin/pangolin-controller-rbac.yaml @@ -0,0 +1,80 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pangolin-controller + namespace: pangolin +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pangolin-controller +rules: + # CRD discovery + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - get + - list + # IngressClass discovery + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + # Traefik CRDs - full CRUD + - apiGroups: + - traefik.containo.us + - traefik.io + resources: + - ingressroutes + - ingressroutetcps + - ingressrouteudps + - middlewares + - middlewaretcps + - traefikservices + - serverstransports + - serverstransporttcps + - tlsoptions + - tlsstores + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + # Leases for leader election + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - create + - update + # Events for logging + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: pangolin-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: pangolin-controller +subjects: + - kind: ServiceAccount + name: pangolin-controller + namespace: pangolin diff --git a/apps/pangolin/pangolin-controller-service.yaml b/apps/pangolin/pangolin-controller-service.yaml new file mode 100644 index 0000000..815bbe0 --- /dev/null +++ b/apps/pangolin/pangolin-controller-service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: pangolin-controller-metrics + namespace: pangolin + labels: + app: pangolin-controller + component: controller +spec: + type: ClusterIP + ports: + - name: metrics + port: 8080 + targetPort: 8080 + protocol: TCP + selector: + app: pangolin-controller + component: controller diff --git a/apps/pangolin/pangolin-deployment.yaml b/apps/pangolin/pangolin-deployment.yaml new file mode 100644 index 0000000..08917ae --- /dev/null +++ b/apps/pangolin/pangolin-deployment.yaml @@ -0,0 +1,221 @@ +--- +# Pangolin Data PersistentVolumeClaim (retained for pangolin runtime files at /app/config) +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pangolin-data + namespace: pangolin + annotations: + argocd.argoproj.io/sync-options: Delete=false +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi +--- +# Pangolin Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pangolin + namespace: pangolin +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: pangolin + template: + metadata: + labels: + app: pangolin + spec: + initContainers: + - name: wait-for-postgres + image: postgres:17.11 + command: + - /bin/sh + - -c + - | + set -e + until pg_isready -h pangolin-postgres.pangolin.svc.cluster.local -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB"; do + echo "waiting for postgres..." + sleep 2 + done + echo "postgres is ready" + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: postgres-user + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: postgres-db + - name: render-config + image: busybox:1.38.0 + command: + - /bin/sh + - -c + - | + set -e + # Escape sed replacement metacharacters in the connection string + esc=$(printf '%s' "$DATABASE_URL" | sed -e 's/[\/&|]/\\&/g') + sed "s|__DATABASE_URL__|$esc|" /tmpl/config.yml.tmpl > /rendered/config.yml + echo "rendered config.yml (secrets redacted):" + sed 's|connection_string:.*|connection_string: |' /rendered/config.yml + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: connection-string + volumeMounts: + - name: config-tmpl + mountPath: /tmpl + - name: config-rendered + mountPath: /rendered + containers: + - name: pangolin + image: fosrl/pangolin:postgresql-1.21.1 + ports: + - containerPort: 3000 + name: api + - containerPort: 3002 + name: http + env: + - name: PANGOLIN_URL + value: "https://pangolin.basicstack.de" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: connection-string + # OIDC Configuration + - name: OIDC_ENABLED + value: "true" + - name: OIDC_ISSUER + value: "https://auth.basicstack.de" + - name: OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: pangolin-secrets + key: oidc-client-id + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: pangolin-secrets + key: oidc-client-secret + - name: OIDC_CALLBACK_URL + value: "https://pangolin.basicstack.de/auth/callback" + - name: OIDC_SCOPES + value: "openid profile email groups" + # Admin Credentials + - name: ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: pangolin-secrets + key: admin-password + volumeMounts: + - name: pangolin-data + mountPath: /app/config + - name: config-rendered + mountPath: /app/config/config.yml + subPath: config.yml + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + httpGet: + path: / + port: 3002 + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + readinessProbe: + httpGet: + path: / + port: 3002 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumes: + - name: pangolin-data + persistentVolumeClaim: + claimName: pangolin-data + - name: config-tmpl + configMap: + name: pangolin-config + - name: config-rendered + emptyDir: {} +--- +# Pangolin Service +apiVersion: v1 +kind: Service +metadata: + name: pangolin + namespace: pangolin +spec: + type: ClusterIP + selector: + app: pangolin + ports: + - name: api + port: 3000 + targetPort: 3000 + protocol: TCP + # Internal API (used by pangolin-kube-controller for /api/v1/traefik-config). + # This port has no auth by design — never route it through the public Ingress. + - name: internal + port: 3001 + targetPort: 3001 + protocol: TCP + - name: http + port: 3002 + targetPort: 3002 + protocol: TCP +--- +# Pangolin Ingress +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: pangolin + namespace: pangolin + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-http01" + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure +spec: + ingressClassName: traefik + tls: + - hosts: + - pangolin.basicstack.de + secretName: pangolin-tls + rules: + - host: pangolin.basicstack.de + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: pangolin + port: + number: 3000 + - path: / + pathType: Prefix + backend: + service: + name: pangolin + port: + number: 3002 diff --git a/apps/pangolin/pangolin-paperclip-configuration-guide-community.md b/apps/pangolin/pangolin-paperclip-configuration-guide-community.md new file mode 100644 index 0000000..d3fa022 --- /dev/null +++ b/apps/pangolin/pangolin-paperclip-configuration-guide-community.md @@ -0,0 +1,460 @@ +# Pangolin Community Edition: Configuration Guide for paperclip.basicstack.de + +**Task:** DEV-400 - Configure access policy and routing for paperclip.basicstack.de +**Edition:** Community Edition (Self-Hosted) +**Date:** 2026-08-02 + +## Overview + +This guide provides step-by-step instructions for configuring Pangolin Community Edition to protect paperclip.basicstack.de with authentication and routing. The Community Edition has some limitations compared to Enterprise Edition, so this guide focuses on features available in the free tier. + +## Community Edition Limitations + +The following features mentioned in the original guide are **Enterprise-only** and will appear locked in your UI ([source](https://docs.pangolin.net/self-host/enterprise-edition)): + +- ❌ **Global/Shared Resource Policies** - Enterprise only +- ❌ **Advanced RBAC with multiple roles** - Enterprise only +- ❌ **External identity providers for user management** - Enterprise only +- ❌ **Multi-organization support** - Enterprise only +- ❌ **Health checks** - May be Enterprise only +- ❌ **Geoblocking** - May be Enterprise only + +## What Community Edition CAN Do + +✅ **Basic authentication** via username/password +✅ **OIDC authentication** (already configured with Pocket-ID) +✅ **Resource-level access control** (per-resource settings) +✅ **HTTP/HTTPS reverse proxy** with automatic TLS +✅ **Basic routing and path matching** + +## Architecture Overview + +1. **Pangolin UI**: Central management interface for defining protected resources +2. **Pangolin API**: Provides Traefik configuration endpoint at `/api/v1/traefik-config` +3. **Pangolin Controller**: Kubernetes controller that polls the API and creates/updates Traefik CRDs +4. **Traefik**: Ingress controller that enforces the routing and authentication rules + +**Flow**: UI Configuration → Pangolin API → Controller → Traefik CRDs → Enforced Access + +--- + +## Prerequisites + +- Pangolin Community Edition is deployed and accessible at https://pangolin.basicstack.de +- OIDC authentication with Pocket-ID is already configured (working) +- You have admin access via local admin account or SSO +- The Pangolin Kubernetes Controller is deployed + +--- + +## Step 1: Access Pangolin Admin UI + +1. Open your browser and navigate to: **https://pangolin.basicstack.de** +2. If you see enterprise-locked features in the UI (grayed out or with lock icons), you can ignore them +3. Sign in using one of these methods: + - **SSO**: Click "Sign in with OIDC" or similar (uses Pocket-ID) + - **Local Admin**: Use the admin username and password if SSO is not working + +--- + +## Step 2: Create HTTP Resource for paperclip.basicstack.de + +In Community Edition, we skip the separate "Policy" creation step and configure access control directly on the resource. + +### 2.1 Navigate to Resources + +1. In the Pangolin UI, find the navigation menu +2. Look for one of these sections: + - **"Resources"** + - **"HTTP Resources"** + - **"Services"** + - **"Proxies"** +3. Click the **"Add"**, **"Create"**, or **"New Resource"** button + +### 2.2 Basic Resource Configuration + +Fill in the resource creation form with these values: + +**Name/Label:** `Paperclip` (or `paperclip-basicstack-de`) + +**Resource Type:** +- Select **"HTTP"** or **"HTTPS"** +- Some UIs may show this as "Public HTTP Resource" or "Reverse Proxy" + +**Domain/Hostname:** +- **Domain:** `paperclip.basicstack.de` +- This must match exactly +- If there's a dropdown for base domain, select `basicstack.de` first + +**Description:** (Optional) +`Paperclip task management and orchestration platform` + +### 2.3 Backend/Target Configuration + +This section defines where traffic should be forwarded after authentication: + +**Target Type:** `Kubernetes Service` or `HTTP/HTTPS URL` + +**Backend URL/Endpoint:** +- If using Kubernetes Service mode: + - **Service Name:** `paperclip` (or the actual service name) + - **Namespace:** `paperclip` (or the actual namespace) + - **Port:** `80` or `3000` (whatever port the Paperclip service uses) + +- If using HTTP URL mode: + - **URL:** `http://paperclip.paperclip.svc.cluster.local` (adjust namespace/service name) + - Or use the cluster IP if known: `http://10.x.x.x:port` + +**Path Configuration:** +- **Match Path:** `/` (matches all paths - root and everything under it) +- **Strip Prefix:** Unchecked/No (preserve the full path) + +> **Note:** You'll need to know the correct Kubernetes service name and namespace for Paperclip. If you're unsure, check with the cluster admin or look for the service definition in the Kubernetes manifests. + +### 2.4 SSL/TLS Settings + +**TLS/HTTPS:** +- ✅ **Enable TLS/SSL** +- ✅ **Force HTTPS redirect** (redirect HTTP → HTTPS automatically) +- **Certificate Provider:** Let's Encrypt (should be auto-configured) + +The certificate will be issued automatically via Let's Encrypt after you save the resource. + +### 2.5 Authentication Settings (Community Edition) + +Since Community Edition doesn't support separate policy objects, authentication is configured directly on the resource: + +**Authentication Required:** ✅ **Yes/Enabled** + +**Authentication Method:** +- Select **"SSO"** or **"OIDC"** (uses the Pocket-ID integration already configured) +- If you see a dropdown, it may show "Pocket ID" or "Default OIDC Provider" + +**Access Control** (if available): +- **Allowed Users:** You may be able to add individual user emails: + - `andreas.leinen@basicstack.de` + - `admin@basicstack.de` + - Add any other users who should have access + +- **Allowed Groups:** If the UI supports groups in Community Edition: + - Add group: `pangolin-admins` (if supported) + - This may not work in Community Edition - focus on individual users instead + +**Session/Cookie Settings** (if available): +- **Session Duration:** 24 hours (or whatever the default is) +- **Require Re-authentication:** Not required + +> **Community Edition Limitation:** If you cannot assign groups or create complex access rules, you'll need to add each authorized user individually by email. This is a limitation of the free tier. + +### 2.6 Optional Settings + +**Site Selection:** (if prompted) +- Select the default site or your cluster name +- Usually this is automatic + +**Labels/Tags:** (optional) +- Add labels for organization if the UI supports it +- Example: `environment:production`, `team:engineering` + +### 2.7 Save the Resource + +1. Review all settings +2. Click **"Create"**, **"Save"**, or **"Add Resource"** +3. Wait for the success confirmation message +4. The resource should appear in your resources list + +--- + +## Step 3: Verify Resource Configuration + +### 3.1 Check Resource Status in Pangolin UI + +1. Go back to the **Resources** list +2. Find the **Paperclip** resource +3. Check for these indicators: + - **Status:** Should show as Active, Running, or Online (often a green dot/badge) + - **Domain:** `paperclip.basicstack.de` + - **SSL:** Certificate status (may show "Pending" initially, then "Valid" after 1-2 minutes) + - **Authentication:** Should show as Protected or SSO-enabled + +### 3.2 View Resource Details + +1. Click on the Paperclip resource to open its detail view +2. Verify: + - Authentication is enabled + - Target/backend URL is correct + - TLS is enabled + - The resource is active + +--- + +## Step 4: Verify Kubernetes CRDs (Controller Output) + +After saving the resource in Pangolin, the Kubernetes Controller should automatically create Traefik CRDs within 1-5 minutes (depending on the controller's poll interval). + +**Expected Resources in Kubernetes:** + +These should appear in the `pangolin` namespace (or wherever the controller creates them): + +```bash +# Check for IngressRoute +kubectl get ingressroute -A | grep paperclip + +# Check for Middleware (handles authentication) +kubectl get middleware -A | grep paperclip + +# Optional: Check TraefikService +kubectl get traefikservice -A | grep paperclip +``` + +**What to expect:** +- **IngressRoute** named something like `paperclip-basicstack-de` or `pangolin-paperclip` +- **Middleware** for authentication, named like `paperclip-auth` or `pangolin-paperclip-auth` + +If these don't appear after 5-10 minutes, check the controller logs: + +```bash +kubectl logs -n pangolin -l app=pangolin-controller --tail=100 +``` + +--- + +## Step 5: Test Access + +### 5.1 Test Unauthenticated Access + +1. Open an **incognito/private browser window** (to ensure no cached sessions) +2. Navigate to: **https://paperclip.basicstack.de** +3. **Expected behavior:** + - You should be redirected to a login page + - Either Pocket-ID (https://auth.basicstack.de) or Pangolin's own login page + - You should NOT see the Paperclip application without logging in + +### 5.2 Test Authenticated Access + +1. In the same incognito window, log in with an authorized account: + - Email: `andreas.leinen@basicstack.de` (or other authorized user) + - Use your Pocket-ID password +2. After successful authentication: + - You should be redirected back to https://paperclip.basicstack.de + - The Paperclip application should load + - You should NOT see any "Access Denied" errors + +### 5.3 Test Unauthorized User (if possible) + +1. Use a Pocket-ID account that is **not** in the allowed users list +2. Try to access https://paperclip.basicstack.de +3. **Expected behavior:** + - Authentication to Pocket-ID succeeds (user can log in) + - But Pangolin denies access (403 Forbidden or similar) + +> **Note:** This test only works if you have another Pocket-ID user account available. If you only added specific users, anyone else should be blocked. + +--- + +## Step 6: Confirm DNS and Network Path + +If you can't access the site at all (not even a redirect): + +1. **Check DNS:** + ```bash + dig paperclip.basicstack.de + nslookup paperclip.basicstack.de + ``` + Should resolve to your cluster's ingress IP + +2. **Check Traefik routing:** + ```bash + kubectl get ingress -A | grep paperclip + kubectl get ingressroute -A | grep paperclip + ``` + +3. **Check TLS certificate:** + ```bash + kubectl get certificate -A | grep paperclip + ``` + Should show Ready=True after a few minutes + +--- + +## Troubleshooting + +### Issue: Cannot create resource - "Shared policy required" or similar error + +**Solution:** This is an enterprise feature warning. In Community Edition: +- Do NOT try to create a shared/global policy +- Configure authentication directly on the resource itself +- Add individual users instead of groups if group support is locked + +### Issue: "External identity provider" option is grayed out + +**Solution:** This is expected in Community Edition. However: +- OIDC authentication via environment variables (already configured) still works +- You can use the OIDC provider that's already set up at the application level +- You just can't manage multiple IDPs or switch providers via the UI + +### Issue: Resource created but no Kubernetes CRDs appear + +**Solution:** +1. Check controller status: + ```bash + kubectl get pods -n pangolin -l app=pangolin-controller + kubectl logs -n pangolin -l app=pangolin-controller + ``` +2. Verify controller configuration in the ConfigMap: + ```bash + kubectl get configmap -n pangolin pangolin-controller-config -o yaml + ``` +3. Ensure the controller can reach the Pangolin API: + - Check network policies + - Verify the API endpoint URL in controller config +4. Check RBAC permissions: + ```bash + kubectl get serviceaccount,role,rolebinding -n pangolin | grep controller + ``` + +### Issue: SSL certificate not issuing + +**Solution:** +- Wait 2-5 minutes - Let's Encrypt can be slow +- Check cert-manager logs if you're using it +- Verify DNS is resolving correctly +- Check if there's a rate limit issue (Let's Encrypt has daily limits) + +### Issue: Authentication loop (keeps redirecting to login) + +**Solution:** +- Check browser cookies - clear cookies for `pangolin.basicstack.de` and `auth.basicstack.de` +- Verify the OIDC callback URL is correct: `https://pangolin.basicstack.de/auth/callback` +- Check Pangolin logs for OIDC errors: + ```bash + kubectl logs -n pangolin -l app=pangolin + ``` +- Verify the OIDC client ID and secret are correct in Pocket-ID + +### Issue: 403 Forbidden for all users (even authorized ones) + +**Solution:** +- Check the allowed users list on the resource +- Verify the email address format matches exactly +- Try adding the user's email again (UI bug workaround) +- Check if there's an IP allowlist blocking you +- Review Pangolin logs for authorization errors + +--- + +## Health Check Criteria (Task DEV-400) + +✅ **Access control configured for paperclip.basicstack.de:** +- Resource created with authentication enabled +- Authorized users specified (or group, if supported) + +✅ **Routing configured:** +- HTTP resource points to correct backend +- TLS enabled with automatic certificate +- Path matching set to root (`/`) + +✅ **Traefik CRDs generated:** +- IngressRoute created by controller +- Middleware created for authentication +- Resources visible in Kubernetes + +--- + +## Community Edition Workarounds + +Since you're running Community Edition, here are some workarounds for enterprise features: + +### Group-based Access Control +- **Enterprise:** Assign `pangolin-admins` group to policy +- **Community Workaround:** Add each user's email individually to the resource +- **Better Solution:** Consider upgrading to Enterprise (free for businesses < $100K revenue) ([source](https://linuxiac.com/pangolin-reverse-proxy-moves-to-dual-licensing-with-new-enterprise-edition/)) + +### Hide Enterprise UI Elements +If the locked enterprise features are cluttering your UI, you can hide them: + +1. Edit the Pangolin ConfigMap: + ```bash + kubectl edit configmap -n pangolin pangolin-config + ``` + +2. Add this to the `config.yml` section: + ```yaml + flags: + disable_enterprise_features: true + ``` + +3. Restart Pangolin: + ```bash + kubectl rollout restart deployment -n pangolin pangolin + ``` + +This will hide enterprise-only features from the UI entirely ([source](https://darkwebinformer.com/pangolin-the-self-hosted-tunneled-reverse-proxy-thats-quietly-replacing-cloudflare-tunnels/)). + +--- + +## Upgrading to Enterprise Edition (Optional) + +If you need the locked features, Enterprise Edition is free for: +- Personal and hobbyist use +- Businesses making less than $100K USD gross annual revenue + +**To upgrade:** + +1. Update the image in your deployment: + ```yaml + image: fosrl/pangolin:ee-latest # instead of fosrl/pangolin:1.21.1 + ``` + +2. Apply the change and restart + +3. Access the admin panel at `/admin/license` + +4. Enter your license key (free tier available) + +This unlocks: +- External identity providers and advanced RBAC +- Multi-organization support and custom branding +- Shared/global policies +- Enhanced monitoring and SIEM streaming +- Priority support + +More info: [Pangolin Enterprise Edition Docs](https://docs.pangolin.net/self-host/enterprise-edition) + +--- + +## Next Steps + +Once configuration is complete: + +1. ✅ Verify the resource appears in Pangolin UI +2. ✅ Confirm Kubernetes CRDs were created by the controller +3. ✅ Test unauthenticated access (should redirect to login) +4. ✅ Test authenticated access (should reach Paperclip app) +5. ✅ Mark DEV-400 as complete with verification notes +6. 📝 Document any UI differences or issues encountered +7. 📊 Monitor logs for authentication/routing errors during initial rollout + +--- + +## Additional Resources + +- **Pangolin Community vs Enterprise:** [LinuxIAC Article](https://linuxiac.com/pangolin-reverse-proxy-moves-to-dual-licensing-with-new-enterprise-edition/) +- **Pangolin Documentation:** [docs.pangolin.net](https://docs.pangolin.net/) +- **Pangolin GitHub:** [github.com/fosrl/pangolin](https://github.com/fosrl/pangolin) +- **Pocket-ID Integration:** [pocket-id.org/docs/client-examples/pangolin](https://pocket-id.org/docs/client-examples/pangolin) + +--- + +## Notes for CTO + +- The exact UI layout may vary - version 1.21.1 is deployed +- Community Edition limitations are well-documented but may change in future releases +- If the CEO encounters UI elements not described here, look for conceptually similar options +- Screenshots or specific UI questions can be addressed in DEV-400 comments +- **IMPORTANT:** Do not reconfigure OIDC/SSO settings - the Pocket-ID integration is already working + +--- + +**Last Updated:** 2026-08-02 +**For:** DEV-400 - Pangolin Community Edition Configuration diff --git a/apps/pangolin/pangolin-postgres-secrets-sealed.yaml b/apps/pangolin/pangolin-postgres-secrets-sealed.yaml new file mode 100644 index 0000000..b61affa --- /dev/null +++ b/apps/pangolin/pangolin-postgres-secrets-sealed.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: pangolin-postgres-secrets + namespace: pangolin +spec: + encryptedData: + connection-string: AgBxITAqIcO4rqdcbJSRdVJv5TO2teg4tjgXsXeB7hSLxcFTLST/4wQPigaf1flrxMlWejbsmP7n2VXy6KOqwskcCHF/KR+7usGD4xAeIsM1LZDi1NLcFt7RVnR+vhoUcU6+F8xm5Tc6QCZnwfJBEoP2yVeZSIj9XwTUYeluoZF32FD54lQatMphPl4nD7AjLC2SD6i9pB46f5h+zWfYUrtoMk0uNrnxPLpM8YnOXZHl8cLOcXAuGu7iWhQMRfCYv7Nt2/HnDNVVjoZ7qcfTRPF4snjOePBYsxVynmZojtHroGosaFGY/FUWkMEB8j7BCQiV4hlVAAuHpGwSZs37+kDOJY4z1MOriXIqbbQoLvhf8rxArly0TJK+lzuhsu1MsJUPuk/wahcgXI/aN4G98RuJUpxxiUk3EZZ8/IN0E2Kswx59zFhmOjmdxw4eh2oam/Oukc/gaRUs2+Y+/j10JaNQ9yNBCQjCfumw56gNifb1ub6ALC80c2jKGhRaDu8tiG2hWW57VaHFMOuZNQj1a4gcsJNqzTu/oDk1505SiowfQbqJYC21AH9kj/OAEcu0nMAlhSlMpM5tWF/m+VmodSPxq1g3b5/bSJdzXKdQ5WN0PE3LvuneESP3B1YBp58Bb4A4buV3kxpSDAdgmd86uN9g24ZdwxlDWsrFZRYaZh1JnnR84bqqyWwHuElDRpDvO14gpjlD4lNTQfScgJ2z+hmnGddjT7u3atCQu4pFEIzcbg2JVi38Pz5JlwzR1crZfP9tQh/CWrIVOQJI+EQE+MZTczBanlN/o8zf6Q2plqnV0QEDqAVtRwLh5OqsRMPuEXDZ/jJei+N/K0KUhTO26DhDXg== + postgres-db: AgAeCwjNgP6NXncaN7CNMDuI7R6f00qalC08uhy4Kxsc+4twWp+K8x7JGiVosNdirNVRLSrGXaf53Onn2f/uDkgWU8tJETIS/qae1QcxSL3DDtC35XxdymYOPlS+97YBw3Lud0mTVda25v6vnfB5aPiNwXL4u4Nct/eYw15bpVmv2zTb7bNbbdNyu5MSiaA14Wlc/9jMXHnJTeGLEno4FEwzaUBFwEmQRyOE7XQ/z2Fb/47xTXnkn29roe2yHX+25lqC7OWx3F9hVVhUon34u/Y4ybuuUwJix7mx3DB2ObKYV8FC939nqEfNw5vraO6B4ICXPTi/hRXT3kWfRZCQ3XHFoUWU5uD4Skp2d10YarsxGxpLq0ZP/CMD51Pr7jbAYQ9WwbLvhwYHv0CjuqrZrI8ln7Y+CT/5l9zREhbqDSBLkPIUfsJXNEmnN3IYM5yi2LkUTSswgRmJD3ouSZTEMHpo7sFoDCm4xzD8o+H5ImIL3k4mBHjgQvbHD8p0NfP8jeLb3PlVgoF3qUD0l5Ekg/5MdA49LdD6UK0ma5kT/wEzfXiWPOa/xwvv7yhV3qQru62/lFMaYs/S1o0gJrE7MzXvM/0i6JnLehrtP0w7LWieV7vx6/n7DAKt1HV4quXRURXrfihDK+fJSbaLkJveAdqW63eB+wA3fnE5j2pWNN7cjj1tFOHdL1x2WOhO2XVt5bZbL0jji1D4Wg== + postgres-password: AgCBmdJimG15nZc6fTW06PbUgxUzOgaWc53hxXVbkewSX+/of+RGdkwGbB95f5ShVSnApgvptPHiFsi+LD6w3nsI37Yl4/uFbwmeIL9iL2wFQsB8lHpFAq8xg1lUK0bsmNvAfyXVhTwlPXLNk4nSPQbLTekE/hdZmtDo9AtBphZW13OSNl6exEKeYEw7gEDSRu/M7c0OG2cxY4GmPqXNMKkbNh1YVcFnRhOt9pXtMbDlwWj66ua/GNRMEltAfGWiJiuKKXwbyTip0C8SCwsMad411B88oJDneLpccwFxq7v8WKTSYLy8hZJEIphhC11sOs+wXOnuGhWv/QpK3GU3HLrPst2eSBwc9B+7mDhHJI6JrngBNBO/356HuGA2A/AxNeRxMW42jU+I60iQjGiDE/IwpdAtOH/Mu0o48tUn3COhwbf/cyC06gvUirWgSZKE5lsuO73VCn/CE0FvxA78dnSs0otw01mkXnHzRZrcMlglkL+BX+7vNamhaZxuG+ggYlgGscYHXMrhxfWqpBmO9ENYweHDNeHfeY9FlD3jt5GDSF8eKDz5JY3qoB2CLkblyEcPEE1YHfMGPFLPlF7t1C3FTASlp+Iw2UP00vJ9Yg3s9qO3CmMkokWsBzG0hGWETQhU8J90y48xtH+m9rd8rH+IxH5z9z3KlPa4KQ5FpjrsQ7YJ1i9TCCGeu8HR5/9m1Fq7j+uKgqE+UmfK6MFLTcm3hI9Q1SJgtucp9yJpQVNhdg== + postgres-user: AgCIy54/EVmkpFqGSu95+l7wKYLwRALNCh5SC0dC6q6Q1VzVyZSP8ioUmKRyJT8+vuFwke9veXcTWm1fy0N4x64/s+yJO6eHOvJ2wSsKKiqv+m2AjdX2K72lB7CGHfrfYAH/8mHN0PdkF2loJRLCgcbeRoNEoWNiiP1qS2xzPNBMIH5Dpl5nlA7/wr4sZAWUfITl4Cb1sGksxQqFcy9Ua4HxDSBuIYMEx4CI7oifbVOwFXxm1oIWFqO4ANgO61OI5uKZNGLnpXifnuiITaulaCa+48gm8rg+fhvw1XTQLVnP80DfdsaU1rWu6wxZj3+Vs/S/szuLMx3OmR3HsgdYxj1mY70ZfV/r+qe/Fu+FFFc/GFNF01ZQTWrF+IaaNWUolPhm7NtLKxDy6FmF/Vwvt23Q6fA11pduxqnZlS+0kh+TGmX6s9dtNYcyuaEXfG3/5bPE/DvYEKRR8tAyoQxQTVgKs8SttSuwf+g5y1KkhwlkJqYhu+BPTG7fVVbL+UEceF6EeNc+LVZQTcx0SzLFxEIyUFPZktUAnLIft+S9n3jZCetvzch53DC7Cm5epKvovGisaeCNZ6wGisCL6UKxLa3g3YRCT/tgJsL8SyLE6g+AQsdVnyLJ58VdwqSrD7tLge9aYtJZ4YGHJzV9oN7zWEp15j3fLaQjNLKptzRoPf/VFgdYs0+PTXDDl2Y0WGCoXy6ynyr73KleMg== + template: + metadata: + creationTimestamp: null + name: pangolin-postgres-secrets + namespace: pangolin diff --git a/apps/pangolin/pangolin-secrets-sealed.yaml b/apps/pangolin/pangolin-secrets-sealed.yaml new file mode 100644 index 0000000..eeff103 --- /dev/null +++ b/apps/pangolin/pangolin-secrets-sealed.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: pangolin-secrets + namespace: pangolin +spec: + encryptedData: + admin-password: AgCdZss9e7VyQTcs7TqsX6B1VuieMdBM0N1zf5xWXdNsNKLaqOdrqg/w/ZQTG5LaValIY5Jf2ghfNf3qhYdmH9jZk01KUCEUoX8FTd56ka6NRlFmos9DjYWGad3DjUlo3nFRgAv04hZ33hKCMZNuyPab1+W1OD+Lw/ohobVae8YsOFSNCf8XmttR78010mtMAFqX5zy/ylHN2Ht3XdH7WwNrcfkJvkASpExipJjOBYBG14apKoe6L+idXH73Raa+3AhQWSeQjaa6WB9u5RvfNSyPYxgGfXp8H5PfOFbL2kFxjqKm73VDmlZ4i/hK061iswtX7jzeORGMX7e0qlN+N2dxHDBop59fQ8Z8WiW/PU1WLo7OcrG/c4RswyWanb//DD+1VMGqxBxFE3yng/TKUKNOIbX5SR2pCHHkrBPVxq8vgmZTJdUxtlvLWO4UQBgHjtDJqTNRyj6bb+/ebjfACU+WQPK/DmSp3FWtpEpOR3k7cMN01BjyyOv4jVMgzvmHOXKHm9S0aT617nYfCCh5UFbsBMNFQRydVQyvLcpom6fqKPm0voX6TzyuvShAOIbRGr8fS/vnjZW2RoajiMEZpgYd5q/aDQwSbBkrBVYx0kPFxw/J6iyV3puyCvhEjHShRefWmjqTT+aJy/XkP7svQFqHDZLBUWvCZdtIAorNqbSmZ+m6qbYbUpLVZ54ktqiQgeGAXZIOhbirAhgb2ygNf6ChJ/496zujEzTWqeOpI4I80h7AUJNVPQYsNYBXjg== + oidc-client-id: AgB1ySL0hutCIK8fa+8KY2RO+NtAez+QB0RSPi1TnJCxQfWkOgICs0ammNTBDJTt/7uEIn3+BLNPvDAh1N9adtsOfDiGiLAAq4PJDTHZo9LdN2iTybVo3b2TmIUlH5pP8mOc07Hxafl7ffcQRmFG8JaCO36KOAhPMpJRcetBet2JqEM22PUXXM+ywJ9hbOCIOizJANZROMEy8AHCBrhrL7Fk9rftwlhoAfh+DVbxOKvKdp0FyeE/RFHMWq0bifvyqmROdSESKgk3gMMnqpu9f8mZOXaHCljJLPF8KxIfp4ezpZPA5xQMAfv771VPwS11RHVc3f4OFwxvXrBpkOqK2N1GkI5ns5T5nCWESfeO9ebVq0SiVhtvLW2rZZQdS3/kdMPHerEZeNrxnm/0QvvvnOz8ZmLXulWLK7bUEndOx15kgf2VFTnTOOVSsHmZAKvLVD2NDQtbD4u6OuiOuwoewuPv33Z77mpJT1RhCtYmnN37cJrDUqaD9HwIUvqEjcefOLUXbwBNHqajBrPGWBsMciWxcZyAeXMDsFTHOZgj6i4Ua7qbl0vIbtHU7p7ik9oQSIF46493HAOMaUU1F3fUNJbmZKWr5Z1SifvYm7dLsXQDUMXKXCGj8nC/jUU5JHLnHCQRPKQ7tdN1dEum60x68riU+x6ZAchZ9XL27ecranXAVIgpqNW/6Ss1UWEoVbiNoU21HFtjyZ0RrUGJxTwI6TimOU7MYgfaiQ4fnnQK56yMlIpDhnw= + oidc-client-secret: AgB1lGh6Moy0TgMM2+8Vo0Q6mvYF44AGR9xetmXpA1Li+FUq+EtdMENePVeL3WtC0H0C01EF16Zdje4YKgf8XQvClaxongS/yWYhHghOj5l5ZuHsimYt6/aPYWpyiBuOHKzAv4OlXxMxtUi5OQ2D8Ntg/iJd1FeQMvmOqFmuX0XBIRpFqCiLmschbfkBVX0u10Gb32YlV+5nVYu9E3jFDYnSxz+TnivXgaRmQUcYA9+/GzGSkVYOpRT3vzfFHdIAi7ZfXXecj+VPXX66yKwsqbwph1+0GUtNOsH9BZ8SNtVqMxkIwh+8IXJS99nL5WI8Imenq7tfTehHHQnh1HMQYAPZwHkg26l1+Jq90nL0jY+nEbtsAcz/NNIopIFWaoOWVaiQx/i4aAEF2a1TilZEZ/Jq14b+U4HeXVuGkIGiSejiF0HA8JdgUpWWtcNnSD8X1a/GCk/27rMcTq0GCUN1E2+CE07fQVr0ya75GV5e823Hg4y8EKES4CBxrj4HiSarNhdLMFbuT2houNYwdFInxkWfIPAKtsZikPL2jQUzv2eQpNF7r/O9N6NcIEoW91PU4lmM8MoS/Zn4DEuDBQTqpVTCUOtBd2EwjJCKWMuF9b88+R8xL99NzuMqSTcKOy5DHnG0EV9Gv1GFWxcdg2m+1ODU6q1WKYuigwgoZDsgiJy9USqNdQfQscmNtn0BqpUQmFOiLD9NhaAOcJkY1vMOu94BYiz+WhfpRLNPu0dWtxKINg== + template: + metadata: + creationTimestamp: null + name: pangolin-secrets + namespace: pangolin diff --git a/apps/pangolin/postgres.yaml b/apps/pangolin/postgres.yaml new file mode 100644 index 0000000..f92e818 --- /dev/null +++ b/apps/pangolin/postgres.yaml @@ -0,0 +1,116 @@ +--- +# PostgreSQL PersistentVolumeClaim +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pangolin-postgres-data + namespace: pangolin + annotations: + argocd.argoproj.io/sync-options: Delete=false +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi +--- +# PostgreSQL Service (headless-friendly ClusterIP) +apiVersion: v1 +kind: Service +metadata: + name: pangolin-postgres + namespace: pangolin + labels: + app: pangolin-postgres +spec: + type: ClusterIP + selector: + app: pangolin-postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + protocol: TCP +--- +# PostgreSQL Deployment (single-replica; PVC is RWO) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pangolin-postgres + namespace: pangolin + labels: + app: pangolin-postgres +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: pangolin-postgres + template: + metadata: + labels: + app: pangolin-postgres + spec: + securityContext: + fsGroup: 999 + containers: + - name: postgres + image: postgres:17.11 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: postgres-user + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: postgres-password + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: pangolin-postgres-secrets + key: postgres-db + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 6 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + volumes: + - name: data + persistentVolumeClaim: + claimName: pangolin-postgres-data diff --git a/apps/paperclip/deployment.yaml b/apps/paperclip/deployment.yaml new file mode 100644 index 0000000..d7a82c5 --- /dev/null +++ b/apps/paperclip/deployment.yaml @@ -0,0 +1,128 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paperclip + namespace: paperclip +spec: + replicas: 1 + selector: + matchLabels: + app: paperclip + strategy: + type: Recreate + template: + metadata: + labels: + app: paperclip + spec: + securityContext: + fsGroup: 1000 + fsGroupChangePolicy: Always + initContainers: + - name: fix-permissions + image: busybox:1.38.0 + command: + - sh + - -c + - | + mkdir -p /paperclip/scripts + mkdir -p /paperclip/instances/default/data/run-logs + mkdir -p /paperclip/instances/default/data/runs + mkdir -p /paperclip/instances/default/workspaces + mkdir -p /paperclip/instances/default/companies + chmod -R 777 /paperclip + volumeMounts: + - name: data + mountPath: /paperclip + containers: + - name: paperclip + image: ghcr.io/paperclipai/paperclip:latest + imagePullPolicy: Always + ports: + - containerPort: 3100 + name: http + protocol: TCP + env: + - name: HOST + value: "0.0.0.0" + - name: PORT + value: "3100" + - name: PAPERCLIP_HOME + value: "/paperclip" + - name: PAPERCLIP_PUBLIC_URL + value: "https://paperclip.basicstack.de" + - name: PAPERCLIP_DEPLOYMENT_MODE + value: "authenticated" + - name: PAPERCLIP_DEPLOYMENT_EXPOSURE + value: "private" + - name: PAPERCLIP_MIGRATION_PROMPT + value: "never" + - name: PAPERCLIP_MIGRATION_AUTO_APPLY + value: "true" + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: paperclip-secrets + key: DATABASE_URL + - name: BETTER_AUTH_SECRET + valueFrom: + secretKeyRef: + name: paperclip-secrets + key: BETTER_AUTH_SECRET + - name: AGENT_JWT_SECRET + valueFrom: + secretKeyRef: + name: paperclip-secrets + key: AGENT_JWT_SECRET + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: paperclip-secrets + key: OPENAI_API_KEY + - name: ANTHROPIC_API_KEY + valueFrom: + secretKeyRef: + name: paperclip-secrets + key: ANTHROPIC_API_KEY + volumeMounts: + - name: data + mountPath: /paperclip + readinessProbe: + exec: + command: + - wget + - --quiet + - --tries=1 + - --timeout=3 + - --spider + - http://localhost:3100/api/health + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + livenessProbe: + exec: + command: + - wget + - --quiet + - --tries=1 + - --timeout=3 + - --spider + - http://localhost:3100/api/health + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 200m + memory: 512Mi + volumes: + - name: data + persistentVolumeClaim: + claimName: paperclip-data diff --git a/apps/paperclip/namespace.yaml b/apps/paperclip/namespace.yaml new file mode 100644 index 0000000..d55f65d --- /dev/null +++ b/apps/paperclip/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: paperclip diff --git a/apps/paperclip/paperclip-secrets-sealed.yaml b/apps/paperclip/paperclip-secrets-sealed.yaml index bee7cd4..e66e432 100644 --- a/apps/paperclip/paperclip-secrets-sealed.yaml +++ b/apps/paperclip/paperclip-secrets-sealed.yaml @@ -19,4 +19,6 @@ spec: creationTimestamp: null name: paperclip-secrets namespace: paperclip + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous type: Opaque diff --git a/apps/paperclip/postgres-service.yaml b/apps/paperclip/postgres-service.yaml new file mode 100644 index 0000000..1c7f828 --- /dev/null +++ b/apps/paperclip/postgres-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: paperclip-postgres + namespace: paperclip +spec: + type: ClusterIP + selector: + app: paperclip-postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + protocol: TCP diff --git a/apps/paperclip/postgres-statefulset.yaml b/apps/paperclip/postgres-statefulset.yaml new file mode 100644 index 0000000..d8f4b7a --- /dev/null +++ b/apps/paperclip/postgres-statefulset.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: paperclip-postgres + namespace: paperclip +spec: + serviceName: paperclip-postgres + replicas: 1 + selector: + matchLabels: + app: paperclip-postgres + template: + metadata: + labels: + app: paperclip-postgres + spec: + containers: + - name: postgres + image: postgres:16-alpine + imagePullPolicy: IfNotPresent + ports: + - name: postgres + containerPort: 5432 + protocol: TCP + env: + - name: POSTGRES_DB + value: paperclip + - name: POSTGRES_USER + value: paperclip + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: paperclip-secrets + key: POSTGRES_PASSWORD + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + subPath: pgdata + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U paperclip + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U paperclip + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 3 + volumeClaimTemplates: + - metadata: + name: postgres-data + annotations: + # Prevent Argo CD from deleting this PVC to avoid data loss + argocd.argoproj.io/sync-options: Delete=false + spec: + accessModes: + - ReadWriteOnce + storageClassName: local-path + resources: + requests: + storage: 20Gi diff --git a/apps/paperclip/pvc.yaml b/apps/paperclip/pvc.yaml new file mode 100644 index 0000000..a5900ff --- /dev/null +++ b/apps/paperclip/pvc.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: paperclip-data + namespace: paperclip + annotations: + # Prevent Argo CD from deleting this PVC to avoid data loss + argocd.argoproj.io/sync-options: Delete=false +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi + storageClassName: hcloud-volumes-encrypted + volumeMode: Filesystem diff --git a/apps/paperclip/service.yaml b/apps/paperclip/service.yaml new file mode 100644 index 0000000..78e29f3 --- /dev/null +++ b/apps/paperclip/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: paperclip + namespace: paperclip +spec: + type: ClusterIP + ports: + - name: http + port: 3100 + protocol: TCP + targetPort: 3100 + selector: + app: paperclip diff --git a/apps/passbolt/README.md b/apps/passbolt/README.md new file mode 100644 index 0000000..4499698 --- /dev/null +++ b/apps/passbolt/README.md @@ -0,0 +1,58 @@ +# Passbolt Deployment + +This directory contains the Kubernetes manifests for the Passbolt password manager deployment. + +## Components + +- **MariaDB**: Database backend for Passbolt + - `mariadb-deployment.yaml`: MariaDB deployment + - `mariadb-service.yaml`: MariaDB service + - `mariadb-pvc.yaml`: Persistent volume claim for MariaDB data + - `mariadb-secret-sealed.yaml`: Sealed secret containing database credentials + +- **Passbolt**: Password manager application + - `passbolt-deployment.yaml`: Passbolt application deployment + - `passbolt-service.yaml`: Passbolt service + - `passbolt-pvc.yaml`: Persistent volume claim for Passbolt data (GPG keys, JWT tokens) + - `passbolt-ingress.yaml`: Ingress configuration for HTTPS access + - `passbolt-secret-sealed.yaml`: Sealed secret containing application configuration + +## SMTP Configuration + +Passbolt is configured to send emails through Stalwart mail server: +- SMTP Host: `stalwart-mail.stalwart.svc.cluster.local` +- SMTP Port: `25` +- SMTP Username: `passbolt` +- SMTP Password: Configured via sealed secret +- From Address: `passbolt@basicstack.de` +- TLS: Disabled (internal cluster communication) + +## Initial Setup - Creating the First Admin User + +Once the application is deployed, you can create your first admin user by running the following command: + +```bash +kubectl exec -it -n passbolt -- /bin/bash -c "su -s /bin/bash -c \"bin/cake passbolt register_user -u -f -l -r admin\" www-data" +``` + +Replace the placeholders: +- ``: The name of the Passbolt pod (find it with `kubectl get pods -n passbolt`) +- ``: The admin user's email address +- ``: The admin user's first name +- ``: The admin user's last name + +The command will output a registration link similar to: + +``` +https://passbolt.basicstack.de/setup/install/1eafab88-a17d-4ad8-97af-77a97f5ff552/f097be64-3703-41e2-8ea2-d59cbe1c15bc +``` + +Open this link in your browser to complete the user registration and set up your GPG key. + +## Accessing Passbolt + +Once configured, Passbolt is available at: https://passbolt.basicstack.de + +## ArgoCD Sync + +This application is managed by ArgoCD. All changes should be made through this Git repository to ensure proper GitOps workflow. diff --git a/apps/passbolt/ingress.yaml b/apps/passbolt/ingress.yaml new file mode 100644 index 0000000..bf0a6d1 --- /dev/null +++ b/apps/passbolt/ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: passbolt-ingress + namespace: passbolt + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + rules: + - host: passbolt.basicstack.de + http: + paths: + - backend: + service: + name: passbolt + port: + number: 80 + path: / + pathType: Prefix + tls: + - hosts: + - passbolt.basicstack.de + secretName: passbolt-tls diff --git a/apps/passbolt/mariadb-deployment.yaml b/apps/passbolt/mariadb-deployment.yaml new file mode 100644 index 0000000..9da055d --- /dev/null +++ b/apps/passbolt/mariadb-deployment.yaml @@ -0,0 +1,67 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mariadb + namespace: passbolt +spec: + replicas: 1 + selector: + matchLabels: + app: mariadb + template: + metadata: + labels: + app: mariadb + spec: + containers: + - name: mariadb + image: mariadb:10.11 + ports: + - containerPort: 3306 + name: mysql + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mariadb-secret + key: MYSQL_ROOT_PASSWORD + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: mariadb-secret + key: MYSQL_DATABASE + - name: MYSQL_USER + valueFrom: + secretKeyRef: + name: mariadb-secret + key: MYSQL_USER + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: mariadb-secret + key: MYSQL_PASSWORD + volumeMounts: + - name: mariadb-data + mountPath: /var/lib/mysql + livenessProbe: + exec: + command: + - mysqladmin + - ping + - -h + - localhost + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - mysqladmin + - ping + - -h + - localhost + initialDelaySeconds: 10 + periodSeconds: 5 + volumes: + - name: mariadb-data + persistentVolumeClaim: + claimName: mariadb-pvc diff --git a/apps/passbolt/mariadb-pvc.yaml b/apps/passbolt/mariadb-pvc.yaml new file mode 100644 index 0000000..db31c46 --- /dev/null +++ b/apps/passbolt/mariadb-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mariadb-pvc + namespace: passbolt +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: hcloud-volumes-encrypted diff --git a/apps/passbolt/mariadb-secret-sealed.yaml b/apps/passbolt/mariadb-secret-sealed.yaml index ffe7356..eb7102b 100644 --- a/apps/passbolt/mariadb-secret-sealed.yaml +++ b/apps/passbolt/mariadb-secret-sealed.yaml @@ -13,6 +13,8 @@ spec: MYSQL_USER: AgAqVEl1q4ij+DOB/zcgerYrpvxd+wlC6aWDJDfeuApwB6bi5KsElPX7YSwy/tgUag3Aay54mbn5wPlmPFVnK4rkWqRm4JXM+zZiRnR0iTKbIYo1esHRIO+tC4YaVVmgewoHe+rlwvoV+01puyjsi63BsBZmS9m7aEpDU9pk5+oHjDee+0YGZ52yFt6As1J/si4Gk5DHYxwDY80GlhsdxMSzNE6vbCgVy/blU//WO1AQ5JKDyt8VhpwpzdCgfiQqZI5dN07URloFjVs66DW54xzXVBa/+2krWNV+eTmykHIrYDDS+lMbxfzH0SkvP1AbZaJLzOiOAl1O/njEu6FjPF8gib2LtmULRdMwML/9/pStzT8ZEIF2lY3NBCyA855FjTYbSo+++HCSW4/33Lkg5DIQrSqDRY6Zbs16yZdGVQPEqYVyu2ehbS37DQrLRIYvM4vArBTobMvjh6F7GiZ5EQu+7wVxMoOfm9ky3e9EUJWRsVWUKJ3cLA8oq0XADeeGo2AAHBdtV8If/pcaFduVXpiMrZLuBvIzn+5K28yia5YOqz1ju+evdcEsIl9uqFuZ8h65TBWI1bRoLqP87o44Bsg2lCwzGeZwTOqnNaMN28/FOXK61d5eKXna7fWJwS2BhF8DSBMkeuOB/dRByfLst3rc4SPMhbXu7cJTsO6g7BkllRdF7pS4d9PAiD+eCFMS39sF/EAkRCx+8Q== template: metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous creationTimestamp: null name: mariadb-secret namespace: passbolt diff --git a/apps/passbolt/mariadb-service.yaml b/apps/passbolt/mariadb-service.yaml new file mode 100644 index 0000000..aff700e --- /dev/null +++ b/apps/passbolt/mariadb-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: mariadb + namespace: passbolt +spec: + ports: + - port: 3306 + targetPort: 3306 + name: mysql + selector: + app: mariadb diff --git a/apps/passbolt/namespace.yaml b/apps/passbolt/namespace.yaml new file mode 100644 index 0000000..f8b7fbc --- /dev/null +++ b/apps/passbolt/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: passbolt diff --git a/apps/passbolt/passbolt-deployment.yaml b/apps/passbolt/passbolt-deployment.yaml new file mode 100644 index 0000000..9b97beb --- /dev/null +++ b/apps/passbolt/passbolt-deployment.yaml @@ -0,0 +1,127 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: passbolt + namespace: passbolt +spec: + replicas: 1 + selector: + matchLabels: + app: passbolt + template: + metadata: + labels: + app: passbolt + spec: + securityContext: + fsGroup: 33 + initContainers: + - name: fix-permissions + image: busybox:latest + command: + - sh + - -c + - | + chown -R 33:33 /passbolt-data + chmod -R 755 /passbolt-data + mkdir -p /passbolt-data/gpg /passbolt-data/jwt /passbolt-data/tmp + chown -R 33:33 /passbolt-data/gpg /passbolt-data/jwt /passbolt-data/tmp + chmod 700 /passbolt-data/gpg /passbolt-data/jwt + volumeMounts: + - name: passbolt-data + mountPath: /passbolt-data + containers: + - name: passbolt + image: passbolt/passbolt:latest-ce + ports: + - containerPort: 80 + name: http + - containerPort: 443 + name: https + env: + - name: DATASOURCES_DEFAULT_HOST + valueFrom: + secretKeyRef: + name: passbolt-secret + key: DATASOURCES_DEFAULT_HOST + - name: DATASOURCES_DEFAULT_USERNAME + valueFrom: + secretKeyRef: + name: passbolt-secret + key: DATASOURCES_DEFAULT_USERNAME + - name: DATASOURCES_DEFAULT_PASSWORD + valueFrom: + secretKeyRef: + name: passbolt-secret + key: DATASOURCES_DEFAULT_PASSWORD + - name: DATASOURCES_DEFAULT_DATABASE + valueFrom: + secretKeyRef: + name: passbolt-secret + key: DATASOURCES_DEFAULT_DATABASE + - name: APP_FULL_BASE_URL + valueFrom: + secretKeyRef: + name: passbolt-secret + key: APP_FULL_BASE_URL + - name: PASSBOLT_JWT_SERVER_EXPONENT + valueFrom: + secretKeyRef: + name: passbolt-secret + key: PASSBOLT_JWT_SERVER_EXPONENT + - name: EMAIL_TRANSPORT_DEFAULT_HOST + valueFrom: + secretKeyRef: + name: passbolt-secret + key: EMAIL_TRANSPORT_DEFAULT_HOST + - name: EMAIL_TRANSPORT_DEFAULT_PORT + valueFrom: + secretKeyRef: + name: passbolt-secret + key: EMAIL_TRANSPORT_DEFAULT_PORT + - name: EMAIL_TRANSPORT_DEFAULT_TLS + valueFrom: + secretKeyRef: + name: passbolt-secret + key: EMAIL_TRANSPORT_DEFAULT_TLS + - name: EMAIL_TRANSPORT_DEFAULT_USERNAME + valueFrom: + secretKeyRef: + name: passbolt-secret + key: EMAIL_TRANSPORT_DEFAULT_USERNAME + - name: EMAIL_TRANSPORT_DEFAULT_PASSWORD + valueFrom: + secretKeyRef: + name: passbolt-secret + key: EMAIL_TRANSPORT_DEFAULT_PASSWORD + - name: EMAIL_DEFAULT_FROM + valueFrom: + secretKeyRef: + name: passbolt-secret + key: EMAIL_DEFAULT_FROM + volumeMounts: + - name: passbolt-data + mountPath: /etc/passbolt/gpg + subPath: gpg + - name: passbolt-data + mountPath: /etc/passbolt/jwt + subPath: jwt + - name: passbolt-data + mountPath: /var/www/passbolt/tmp + subPath: tmp + livenessProbe: + httpGet: + path: /healthcheck/status.json + port: 80 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthcheck/status.json + port: 80 + initialDelaySeconds: 30 + periodSeconds: 5 + volumes: + - name: passbolt-data + persistentVolumeClaim: + claimName: passbolt-data-pvc diff --git a/apps/passbolt/passbolt-pvc.yaml b/apps/passbolt/passbolt-pvc.yaml new file mode 100644 index 0000000..d4e3c05 --- /dev/null +++ b/apps/passbolt/passbolt-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: passbolt-data-pvc + namespace: passbolt +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + storageClassName: hcloud-volumes-encrypted diff --git a/apps/passbolt/passbolt-secret-sealed.yaml b/apps/passbolt/passbolt-secret-sealed.yaml index d90cd32..3c81f7c 100644 --- a/apps/passbolt/passbolt-secret-sealed.yaml +++ b/apps/passbolt/passbolt-secret-sealed.yaml @@ -1,4 +1,3 @@ ---- apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: @@ -7,19 +6,22 @@ metadata: namespace: passbolt spec: encryptedData: - APP_FULL_BASE_URL: AgBZHONa3yM1JEUk5G4aX3Z2F7ScfL/u8rRWCgzm7aiYHKLeU46yTLQz+drqpnpQGMU2hoSrSrXvAoPIoABXdGSUnJVS+oZ5LjBE4JlYBaHX3SCEIeEvjzIpdzln35nvvP3nVJP0s9nRiaq/joTy+lN6AUDQm4SnBNWVu4OMHtwI12RwfiCuVfRnO0iGmAMdOVH71tnNl70HaMz6ZnpgF1WSbNpTIYabqJghwqpekSl2SWy8jTvDsVQu+3QoeoTmXmcNRxaizvUbJaq7xVr94BeISMcC4KLNRY37z9tBdD6J92HjgAPVXw8Qxa7vvwCD+jJ0XIzNR2IJSdqjkhr4PKC+AsZ57mCWHKRSgg3+QUO3TkoDhD+dwoDtCmBbv9aVSA+9gnNlS9QAVqsJvYs/KAJ/z51CBEnDyvc/qADa2pEUTvf3pu4wD6yMBpan7TTZQvdZqk5+UbSWkUZzY2NQSrpECRzlKcGa+PJcuqxGcut15dqzDt7Gl5EQBFi45iSJ86gL0O6VqXU8yQJFb62vl+3CLR1EwoTmodqQFSCLx4349/QNl/ZuHEYhn3nN2dkzI078cg1kwah8qb5Y+1HbkRSYraU5HwGWERvG2dmW2zfRV6DQrq4xaeX4UQHBz7+eyz+KIpcyLCc7bPkpSdPnQ4jPcz+mj4sF+Aro2qOlzQTkYPdBYMwU7pkqdYL5zI8vVhsjBIJkwNEcTEg8nBnV3V0u+4FhQ4yYFYppeWlcIJM= - DATASOURCES_DEFAULT_DATABASE: AgAqsuJ/io7Hn+YtaktL47MfmV/h9k/escD6xfHUwtmvPoMP6qH78SB1qymBBxsfy7rozyGM5Lynj+/It4s1aNp/iIcD5XQ+IAh2PkQURIuYBow1D2WIPz/y4aHdIBEZZxX9luvHhV+FnVv4Jo6fGY+vSpjl1/QDvWC4O0B1ZHYvSaSKwbMZ7g6SHJBPO/qIZPzB49+jkH1ko4y/2Cdj8waAWcIrN2a7UlaIpoiKw+HZ8hQoac4xgY5H6RN3wNS9GJ6GIx9ImJdp5oU+RbJzsn40BU3psD1mgDtLlBTFIkLJO9Mo85ASnPguPQKV4d6b4OTy+EJJ1NL3Zi3Tq3U7LNjxx1HdqbQk1J3dxUsi6xd1UNd587MEiQQXVR8JtVpAu+FyPGyIwXBgfNev7YgeT9JBdKusvaCHsuVZPJgBpEeaHfw34eNJijxca4DZNkZKyhJdWUDJUp64vlcGStr5pK2GWm8+ncxF4qjYpRT799HJfoXgq/AmAaAnWNvF9EJmILCU72iqduE8z3DCZbO87NQqifsfp4Nl6MhsEfQ6fHGWdOG/WiIpOQUIvVQ4IPhjc9DwQCbvMmGTEphs+1LKm9dpSyYmDiZji2pJ2Btg8VNlGxNCAP9YDuC/+zqxhIvR3++hS8aWZW1qzuILkMrfn8yoqSk9XNIhrJ1AFp12LJZE/JGDyIYNxpaljCT10GWkuXhFqIyyt+dxnQ== - DATASOURCES_DEFAULT_HOST: AgAWyQcHR8CUM/t7cGBPO24xLFEdEmcx3rQXdHHMOSrMAkZgKb6FAfvidUO7KdY4O+HOv6218uNVU79nYlonocbNG9lri3Iwqhz2vBjlbctERplC/BabSeJe5r9wcFE/2+lQqfDFAHvoRgbbEHuDrksONcv3Anx3sHysQSzekTv7KThFbZaIhaPlu91In+E17wYzyEIJyZUpN+VIqwIMG75wlWRJM6II7p7amESA4qrgE5zjEmoZph9LWSNwLHrF+7g8L0vluvWjOboFABHCecAEJ7RNxrHQVNBcBNFTowUex29JODlmi9bFohiiB0lL2OJXF9ovGmscRXfUvz3sNk3XBtjPzwTI/gI9kc8JrMvFODsJevf0LfCzpVX9XoZQD2gk8xAzDmifuBhrP3gzChlG7kjxwGJ6WkpvFP7Mzfh4zgXQMUJ1FKtF/wF3nJqQVnKeFGbVzo71UHw2bOvVr7DfKpCMyCmi+pFjR62+1Rw8+7ILHrZKixqHCjnnhR3kotksoNXxRJJ+gQyZTV5BxOyjHGMZtPFD7RVmtpu4vY+1ja4OqieauB05M39Ysz9Cwc3XLdyg1lRxofGWxlsEiBR66J4i1I1s71GLpl46a/FxNz29qfiLGOzanpXso7gtq2vLmukLVoAItYHCpfRW7UibO2lOl3NTj9A/iNVnENslxXfqE99MqPOh2ZxvrIP56w3iU6iBCyxU - DATASOURCES_DEFAULT_PASSWORD: AgBiRSRkk7vU4WrU/8Gc4QJlAS8DVG7kvSBiC9Z+DuGf5guo5pLp/5j3Gcdm3Wu/eJTmn1fg3lUj/I3tUX174VnlEbDEmbkI5j/37HwNMvaqisWppscjVhwTy8yUA6bC6hww6t4H2cZbEUPQmNYQ4M6Gv6Z4TUJmAtqAOyGXw/HpHuz9n3vRHdCUN499SrjomdCvdcW15Mwj6tEj9a4yms+XN7A4c2x2mB+s1G3fq/i5QLH0FflbiZqJqcsKZWmzXgbHkK8CN2TN8hXJqgEDhTEWWd+8143ciZGZOwm0NnqT6dhvmP53dNRTEZDzhz7e9rRm/Gy3SEvckaIyoS1JmoUys8SLYmYD4Hdp2EbVnRaJncKgspg616Tk6CqIXBb0g2UrpZQGfnk+zrz977pupM2hkKcnhq3UB4od+Z+Fx5ttql6FAAcfj6WF/UjrtV29RhPmRIKKU1uuyHLBXTJSS7kYZLGtmJlLJ9L6lrQiMKOq85dUd7NnBYcqSqCr13EiOa0f5XVOOnGAOBb9C8mVQ17sK2BXTfQmavBxB8kejhK7Yc7xTewHirLMswsZo/5cAVOrM6JUIqkyMek681vPYbyMh9EzFy5332MzWs7Ybr8cNeJ95ZKvjV5QJut1s8bsiTAY0ab37nfloeF+1TafcQpQCqCAfKYc/m8rCXI4cO3L2sPUSes1RyGbGSWBfVRMfBqbC2RtHGPuxZNqM/FFFkBhV4TsLuyFjz14NF0OYvOQfgtqoJ6GdxEC1TZN7w== - DATASOURCES_DEFAULT_USERNAME: AgCY34cXlrVdN3ZVXjIgLvsMBTTzDiIGrONPfVkw5pymP+pta5Da7qObJ7E1t8dBeunFwOe3Pt9bpAjF1yr4SqmELrH2ZdVkrA2aVrqV8twuDHKndP2RbhwVQDaA7m4TdlEeC6wPWX1PUiy4Mlry4tEvOruxRHZtRAXIxROCChNBMJYkCXrwu3JV35DeQPKqo9NVDYzJpP6ko2BY5Tu8yv5ndbtuYg54JqSCewT4BDqsjYpSKHxYruQWS1p/2jOjIHqAsx862AmNDI32T3tR/uVkl8acxFAveCdpw+Mvl/1ljSUt/ZJdm6xJIHKjx5rcw4ug/8zzG/st7hcMtbPUkcV3yZ0tS8h2iSwuqehn08XN+e48tEyruBGXOsPYl+iSOlZjAixneaOn4SIFVrbOs3thF/VIVUAN/vzFOh8/exXgL67IfVND4wtOqqHMsFIhxOGU1+pJi7+AfDhoBUjEW9uZk84qPwQnPO+wxoO1GFikKegfaUh+wvN0/fH9z1UdtSPbx8KRgjLevfJLIkcEYsdwpeETmqIIs5qV89GTjgoFrbMknrxhqkb2DqOzfSbrI8HhO9iKEK64Tf8JdH2DSWpC2UUqf/cWZaRDMWD8ojvHdoRE0SorYu4X3x7fLLn7RDf4WWVJFAbgntDB7KVyxX3P+KuPdklctzmQ71hWcTUm8kc8GlMZVPnOb94wcHjs3cHo2/V2VNMqrQ== - EMAIL_DEFAULT_FROM: AgCFY4Xq7FMilyIsWhylcI60xMZPKh0ATurfM2N8mBkO/f5gX9ASKBElvsIn0r+1bW5LMxylzcAZiiFjYbxnRmxE4QnLdt3AqHepPT52m8mJ9EsVpFln/VzDKsZhoGzdYNDS49tzyVYwlq+ZaZcR8DJhEDTPast0QCB6pcaPv29JqVYvrfg3spm5l/pGNJ4/L2/N04U/5swlKhigxNGzanFyOMIkLPLsLQswZwFjKeknWMZRKnZFPBqNqG0Kzo3fdBkyksaY58hlRQJ/MYXWlL2wnYF8cs7lPQZ9bd9/SAJpdou2em1eHCc3akjIoCUIOtPKo5QYBaSNm4Ge0Ul+phGgJNqB2//SExOANn1XDosjTl/zOBqFjhzPEmJp9F3iv/4qTx3PBwPFHe9a2Z3A4PloEBq14Jc5BpGf4Emvu01Jy21SaBXmsLZTfud6GtAzIwgq4awO0wR4rz71UdeioZYXjmMITFLuBd4EjSW5h0G6oz4dM73aJqKAP0O2EZxSnItRE7MVjxMESUCpRBwButPtROXwJr5ByGGXpZyuEisNTk/CWPDxrlvqjE1swKdXtMHLyCfkC/rnPlPWb/d6zf8urNVaIwzeKcY8hZEd9d+8HtzwocnjMavRWtc4NVqpQ/UjTypDH+HZDo/JyPTHaDCrSoEh1LzyaiZx//oAuMrAB3Q7fOFUELiePsE942XqXLvXPs+HHL/p9ioO3SYd2iyP5PKz93zR - EMAIL_TRANSPORT_DEFAULT_HOST: AgBGnI65mbVdqKbXa+dGgex+nATzUi0WUoyixhO3rWktc8f9r0Rczb8hcWy7rhsicuElhX7FL/H0bjk2kFR9Fo0KnVIirvjuvqXeXnryrCWWdAbjYQm2lQDPGoLItGk+Hiix/RZjfe/Ljy4xmCizbwdwE/+rGJMajjsr0t/fGXmLvvQqL/jhr8K9WPVjcolyBwv1o1uB3UNcUaJp5Uo0yaiSfEg1hP7oJlTGwJgvT1Nqt64n16ApEtRSFRiD4ICDLRjGeiyIkNvuDtGOonnle99Nj+uS+iKZZ165qol1dT7uvbT502hiz9Zt9qbr7yTALQ/Z7hZQi212Hhhx9KOehVfOLrZUKW37xfJtaa/aSFqoNjfPXtem55jXDS2XlT0oojH1CH1juOIvXXLQm6b/2YEhkPdWeclEwBNGRzZ7dh80owYeBg8eMWKgzGGaHJrqkkREelEhpyjQSUSu7Q1+b9W31dcUgsGH+wlost9sEX45BIDouUZpiQNe+Ngd7XcF22jPbPznyooWrKO41EEY6OXsnPltJvBtrK/UU7WwzwUeGRi8Y7fBFu6otgLJwY3gHXa2eXT5Bi64+Ug7WWAm+gB5RuhqNXDTis66IC8HU27X0HYtLRQ5TtiUeNKn0mDmSWnoBZMWfHxHCpeEVT08UDHXNvZ+eoSLmMndHD9bnjKnNdga5BL2j6bygf3QVJLtTqm4jhmzvrgsJxN87ivSfVX0U81TF2285AEvKHR38GKa6ov0nsLquicg - EMAIL_TRANSPORT_DEFAULT_PORT: AgBYGMmzj+LqVckAzsRCmjKjCDb2rnpzG7aBECSy6C/IjhPd+Ic6sbSU3jkn3PFV6JJ9YC1KXpp5wUPmWaeI2wp04LksxveNs6ErN8QKqaT81Y/wwZrGby3w1tXngjl1ubnxzlRcLxCoEhSVjZlmDFHZ5CrkKKMHOjXARyJC2x+pCCIdpNgij02TnD8ZMvODl0GaU2SuQcpKPZ0zIh0RcoR1R746RWnZRv7/smAizUdCWtz68wt9Q/YAJgd8G2QjEklCe9rj5uh5yc/qFqmF+wDESMLRCuNAJtmdL3tyi4Mp/rQZxIEvp0BoEvxYJ6JP3mlSgSa41Y+aM1sa72ZXb4uZMiRuTTZ75BPPQzh91dI+zW6oJMOR3SS3yeTwgK09dNbzeIT8G2VGW4I3b/KVnKvJwZvLSSrHMV2f5XBcbibxCKvEeVtvnlf7nK5HfhInF7g6Vckl2wj/D6FceT+Ru1cn9T06AHk+pX4WYCzhalIOfNLVRHa1eFFsXaOaNjzMXpVK/ZvFea55RgR8KlxJHp32NeoHR1tTypVOFHLC84jHRgvI3pX1NMqvSg+oelQ1gOH9+6tiaHf65aKRInv39rIV2yRvV1N0QPbZ6B+wOKrYiJjxQ5GtlLHEORHhsZJ/7Plh4K25F3qDNj5jUMjtRla4G3ebsIzubRTDmmPWsPOLIRk0MNxFdZDPgYYPHD+hiBXgbw== - EMAIL_TRANSPORT_DEFAULT_TLS: AgAQcZPEIqpL5VYWC+32cfWP3gD5N6AanpYKCPBEfwlU942U+Df/CfnfxSByeHmEoUf4X56I0STdoajEYZWka3wQpGfPLfDaFeqYJaRyaUcSBe8GdxrB4miiwr58E1SlmWdMKYMPWsw8uFuD5iVOa8ObIhgDdwJ75njhZVZw2sbzhHBf22UCavfSsIyqAiys1Mnx2WKoCCLg0XDeISbxHcRhFhKchkAdZhOGPTezWdbN74xDxDdpV3a1e7zps35ujMH5kSYIwbaOvyF8tHjfx2T72c8WbZaDpT4TddY4l4Dk8WY3P8QeXYmZVj0zmqJ74UmiICCqm2CNHgOTLr/SiuQg08gvDI5Kep3WvMwOpm2kpti9oBBQ1dER1n9kFEVr2jtYPZ0D5zJfNPcNlkX5Xc6PQdRo70isLoFLeyd4GytwL07Ke3sfzgmuvIV005Oi706Wx/S46rjA0uzd5g4vdTd09PDzCdS/rNNVFDpWzLCgqfdYsYkbCixMRce6MyYuuUiAuZG9aL1BBsm3iGSdjR20fG5HL/A7RsL3yLrHtED3upYXYXi2BdMwTTeAN+f4jtYfBGkiesy/sDlimdRQ43LviXMD4MLbT+aZq9bxh31Lu1SHHAVQvbos0oBpCf91GT2heyJy3e4AUNYZsx8q8rQeqgOUPFExpZ9Sj1FZ/JxxyFOnoF6ofGpaFjcl81bQKUVhj9cAlQ== - PASSBOLT_JWT_SERVER_EXPONENT: AgChtQaje7YWcPBKEQN1Q4d2+PiWyxhp0eU9C9n5OtjXYwa8ejN1MVPQQ1TmHW6ybNjKJBZSbu4bodwj8CMRJd/iGYkmR6iRKAjazg57uWYbZjUanUaZXHhY1AfSy6a6yEpiAUlNpODL8vch8R39dIzlGb40rvG98c20bRwB7grqmCjuiWzshU2KZpkJod15svyGS+CEr/5NV7LsUhsKGt2p9D2zgEEvDEO3IKIghkGa5WG+Fhv6V6u6ESyqPwaO2pj1KVIN7yvyqP8dUCJ3ekJprW7FJRdXkZFWvdWHSpryIG1EXhosLaFidggkYqK38AGokxaKK1fuCW/tABVWAD8Wppo9YiHeznk1kdnAtfkoR485/fd9hfQ/VFs+WthKoytHQBTDaxq/QDwsr/ZyNSxb1ihhJx20tM4k1DtTf7LC+0IlW2zjl5K2U3EZ8K702uSn/w4GnjyZbW3wVdjirRcAh+K8pc7kzZTUEceKRpofBxzwuXhnz40uHtjODYdlysTl+mu2NiGDg3BE6CGusedXyRbhnTTLSndD1I+5NlCGbRI4RhZJrXxL7Ug41l/ho847+617mwNp5Fq4drL2CDNULzX7YKAxLtJMIQCw7W6VpJUyux1x+xtEK64iHy940Hfm8VPozLAZhxdg/fLf7zYePAlDiwr+EbO0Gpasup6x+4qN/juldIPjPpiwDBqmb+wGqLAeUrzgLefBnvKho1RlLu9t24MxASKqMrqErbOQm0tABmGNpLX5nopxRw== + APP_FULL_BASE_URL: AgCBW5OB2Wf+4uhTbrrxt2AmdveWCwDaef+hqfAwZNS6PgkZuzeOU/Avp1hvvlMWQeC3KWc+KjHHFYALB49faQ+neb5wGlT1h23UhorT1hDy7dQ/z0ExkNMV45VkwW6y23ITdQYwZ14xpjdfACamGvBWKYwd5s4BBIwBCsh/TgEqs8PrMfhdXGxQh/GgD3uxA7xuU1pDQ3LnS6UNPaAOxxLfYu3ISEDpMZiI3C+fKDwIv+9Um7BI+f0Y1AkasXDXDQYc2wiHN9y5COeehgkthz+XmlNIDxK3qnyLuFcswdeWzehJRs4p/DyI3Z6fBpM9/UeQXJ+zkCVJx9M4dK2EiyJNiCLSzUIIfqXvemLcZVSwx9VpBdTLNIkSwC3XPFRNmMJkfi7AJzFLDD8z0VflBdxeaPfDeLblgHb4UjqSwEvebFmpx/HoCueBD2tGi/3sGdMEzt9UlkE3j0fwVuYo5owA7O4YTc4L0+jjJBv0S/3GeZAKqUYJm5seC1x8hEWTFqVYEP6stCWq6iNKe8iGC8Fb7lrdf0lpgIaB/9JZ7Yhf4Mqcs33/0XqIRBx5J4oMGW0XbIYZf18gymuy+KSYCNEdrIzhAKJnySbBuFrPj8KWqJn+tZU3IBXAnRI/WM+Vj5ODJyC+17LJ2jZDscBMttSo2qI6n+b4fkSrJh4N9rz5qF2vkLShVvdh6GA8A4Ezf6H5wgP3HLDg7zWnQcquIBh6kn5SUhGHjNseYFPjfqk= + DATASOURCES_DEFAULT_DATABASE: AgBXKOhL59wHbiEpsxen3gwfVsy5RqnjsaklYB+5WxknHm9RX2kLLK1fgMSd2ZPCwK00JDWgqXoiyDukYasoErMEpY+b3tV0z2IrImQylOpL2Rh7b3bP8KJKIVQIVXPkqb68GfXExfA10oYPYk3TeqV/6V3qi3ISprow2hRn0+uI60KwrlMGmluAwYWVyeTG2D0dGKgYfbbd9wj0bwGvnElEuMIfUZjauRwWrZDa5Mf8hN51EEB0u/67WG3GN+mmJlzyoKACoFl5h1u7aHON5Xonpynwt+TZtvqpv3fL+5mU81NobrBA1NM3kVLLgCDaD2gC80QFIP534mOmea35uRuHnlr7m8W+BSEuqeSnQflf0vTOJq2q6ibqHf/KJBvlMYWv5XUjWY3rDuMvxNa5x90ML3JycMYmgpPiEzF2UHP1YW3EPiNshruDOjDN08Dz2WYST41WhcSFeWhDgK0ry+3+AeJjpS3C9GOqII/hpMpfA9w0KrSv4GsazsxB0lIYQlUv0m4T+B1sH2kzSq7LJWZ0GDHRuHy+DmnOIXoecdejRCxeGTrQ6/5EC5KV045NGIYpNWiVXFAogp2xOBQ9WJguDewzSpaUDAdmmMBpHbJkkRfAGMu+CeCMUIGJ/JgSz9oDEGaCHq4p86OMS2AtDD16BMo1OkC4BCpKlq/k+LPjy4wpqd7XwUvoEy8JTTDESQRwMoNKA3PYPg== + DATASOURCES_DEFAULT_HOST: AgAHsiNKVbSdRDBDTGfdSdcl09NUPhbcDkAobdrEP7IZiT6OH9DILEU8O4rNjac3pBa8wIbz/MtcuoAFZ6oWhrX7KnKoz/OnNJ0bH2qTsXPivjNzqqYD/r4ZL40REYX72IfCSestUI+6fffcQi2jsqc4KsWWQas876QS0Vwn5RodyN30Yk0Z2wtIXCi9f3OrtnxkyNGNc/jRUwUppoNWx6c1iKD/MS2Ij9phQLsTznKi4GapIx7LXo0TamTwW5P1KRkuAWYIl/l+v117G/tHNu6pwTq0Ij22krm+h0cETk5HnZXNTsxHHXHIQKa5QC207FdamunTce0Z7ZuTR+HheRT7G4WmiodqX3C+Z6bp9xlbTnNxOQ0xG8UWW1swsDWC4hxv/3G1DIHzQdUC/OX79T9S+RcOI4d9sVOysAiY9fPPmsLx1z/LDG19x90StF6JL7Bf31Do9ummMUwv+iPzQxofa1yYyqGeTZjGjGTmC5aPzsQ0wL/uFFWUK03nIsHSkezqvpr2n+5MOQzbpI0iJvwkgBDDadpr00u4bQy/SupThLTiKpQzBbUS3aPXoB5iE22xq5CQVkjBtQ8hd1VusZ+sVdMWN3tyMwSiQAMNFdHnC/wc8cxrAhb15pRyIr73R5SPx8BsdNdM7lSxe3dYHqQy91Zlw95P6jMIQbZg/sULTeulSiptHcC7w7Uqkruov4jOQRgasj3Q + DATASOURCES_DEFAULT_PASSWORD: AgAUxS5m9x/4AEvqeLQp2Ds4SZCY7nVBQT1335nhLq5p26jigPI3tBS/jDpJauSp4vJeRwox/E0v1JYgFtlgJd+rp6Oe9qBWp8ZXV4Sts1oauKfbWDF53xng3crvhSkvo9LXW0wXxwTON/5Dip0i3QXrZd4KZJA++ETPZgX8mKAaZXnO+NfFk726XyxqHt7cVgNagTBqzD/IZIoWn0idOIx/IHfnxunLglY+cgG5ypChgr5NdQ3VavP8vZLeODRoTEKYwO5AZQ0ZNwdnfJ04vzjKxegjJZjzXs6pU9If+NvEzpKl6q+v+q/PW0FcwBNWVotx3skzGl5ypN8/KwmACKYZM8ysFAEll4Y1U9tbjwzb0eiqctJxkzCECyhlAhsM578QM1VMtnlKQAYVHcqwX6cPOTq8vWpQS2Yl39jamyCqC4/Upqp0MRYT5ilDp3luOV2hEAwOJtjYxGbmf4LLtrLGKb8aFye02Y1OQq2Ty6JVvz0IhawqFuhkmU0yQ3vOxcqkYDgG1stSFDxrZxFvYP25rku0A1Ny03mlk8EveDHPfYgwpBCKnUmuU+RhtkeQt7pCdGel3Vypy4vao/op4Cjc34kz/esXR5odwrTYOLk2TxeztQ8Vg129wm0R0bNy446ADX6wb0Ka8iaZjkQOTYthdiy7iDkL0TrAXcj4BHXhT7Z/9Fb24fbVqPTzFTxVQBBVAhrhHt2MisIYk7wsWV7pIZovxj68REFOx/jTYaxwp8tCojDcYMIWKt7bzg== + DATASOURCES_DEFAULT_USERNAME: AgB5sTglVPfdgaauZvwX8lNsZAACHqm44TxTBgAc0jog6mzb+EmjddofybwtmKDe4YTflHCkFVOEZS/y4VtwBFHI6cCiF7MrZwjD/KFTIDSoNmf17mBCNH46otOFifIARZemGWl9a3jOy96c7uvGTv5tiBwZn2tgD/b2Odl0a+avYAcINjfuy8SrItz61Kl86v+yb9BE2TOHpiamaIqHj7OSqQ9LRv54F49n184aPX0qi55vKnOw9AxKeR4WEft6nQjudU7Z3L0RTQDEh2/aqgle/RUI7vMSSDd6hJhMkcwV8YxDfmOFzzB6KEp1YHVUBu8wOD9AzJyScn+DoiEv3+rG/3ER22p+Mr8Ai17KNHS8k8SyZa5jm8gqwv9exSnR/Une/qlKNt7ykRtjLSPXHtWTssTHttgxnZVsg+ggGiwuBw2Kwo1SXDwfRPaTJv7/VdnB1w/r8fbat2JFQi+FnstJ5sbcKkJKjF4dAW3doSo0Ycfj5/CbcwAMeroyqoIlIvXFhzNICmqEuRjjCxm+LkBiGuacC1C7e5nBPXLmBES/Zr818tdrapnL08RX4ae1Urw3KmERuZI5ElkIXRp1ergy/t9xX7GSbMUmz9H34v7lgZY2hyQkglJrX2TvZ2d103RrHvpSUznp3q/3OSJ8xQSvqD+fAm61EeNSokR4NeTX7AdRqAXU+4JI+aCeDrb7wvXIaSl5ERNGbQ== + EMAIL_DEFAULT_FROM: AgA2UYJrzUW9/M7JaeEpWDLKcjz0wa5xjCJ6CFVfAzkvPDjDgnwGIDOuVzYhfYcaOGOMyTVoJgcdoUbhI0u4nOL7/kbHVqwsxAPNSMLZcSAvZXe9pDW9+htwEFphjsnhmllf0V2pS+MtUwnQDydS4zdsBGgwNB7M/kAF14lmiXUvzC9XuBtvnscLNsDUpE8qlxX++7xEh6FZgCbZXE7NumKZASjFWx1jxQ5/Hx6GmiAIEogGVFUVWn48F7ggK4zrs0OxczGGOcVqvfIedi/HvLkEqT9RraiKmWWyDTw2tq9psZvABT6W9CDsnpz3WBpLYHw6SUwDVgam575ZytQ7yaV4ZuLbbJ1tpyf5B9YM4l9mwnAw2d3uKNvGbN+kqrsl6ijPEGs16xabp0XhtiKTDHrPU7Houa80VeMuwW/OiIRaLLsnOw4TSG/K61v6mN8JI2SkDNQxuqc380/rk+EVw+ok+ndRYmCGfr00Ia93OODV8oHxPV+wNFTna2ms+6eUlFZ+rD2jDaHnSftH4ftfDTgqkTFGrALQDguHj3I73/BfoRuiZDIlkaJtqf2jgH401nOV99vWs0eYnz0/y+Ch1yus6yiBqSfDq4U7mgWzk+8V5JpqwarSHiIGzZyHLyYnC03avezWlp4TRK+J2R/dtvcdEg9BeKw25hn39ZR+/21M0rCOflWAqptIYNHvaxKBQSY3g4/fgLtZJWDgTBx0+9Kn6uTC3YnO + EMAIL_TRANSPORT_DEFAULT_HOST: AgBtqgIksjqjrOY1TSeOhDmsexZL4BYyvuTD9fvVdMriJmlcMklavy63Zr/D6ATKHeXHJftNlp7C59r/ncuO5Rtih69xGH29q6//doXpfSJ8fkE5Q7RMCBkOT7436gMMtB3LA1G7TTS3qLvje8u8UIIv7kkXXnfbtnQbmhE2cr3HGp96IKt3jh9jiq9dhAIae2Ba+uj889TUvJAVZGMKU8DR03OW4eyXKmFCbqrW2iZyI/4TIB8E/rJ2k6DfALAU9GPQTBNjLDErOn1RshZL8XSrgbSBxmU4Blxe+MswPHkFZgMbnMtIkzbHVKA+veTDyHnBGE7Ygna+LTTVzszb8DFqOija2snzlGmaGTmbQHBZpt0NegnqtUmW38+Ie0Q62NKlsMN6hZCn85bgj5YHI+iO4NDugZRSYKwIaX98L5RXlhYQlXXqCvYIdEapJ4bBuknU5g/Nj0sB5okG9HWzsL4NwhYAHy/+6uQsEtzy+TjCMskNo0yqP5TJsl6NcUIjj8xICZhrZSkgCpyXCYOVcFX2TicI4gDe2lwYEPVuUH0ZL6UsqXxyJNYbWX3irReeXIflosijmS3WmmVsjQOk3GKYHZDWX4w+bd4y+baiz/U8xuK4GCbDWQHCRx2XWOcJK2OzGwtI45M7fHKL8jw4VbEOrsZEmPCCxO1N+BjFZBPjJf3QvfdwV2h+2zxAUZwqqLeGagq421PvPxlePcgaT55WLbobG6NGk52rMtNSlqOgpvWmtcHALErQ + EMAIL_TRANSPORT_DEFAULT_PASSWORD: AgBV3Ipx/0YRpvnFDjPC/VIHC3/Rj6CQ1Wv6FbBOjpMrKWU+RmpC+KBQpKuk5BU/M3GTceK4p+oEkxlDDdd6ikErvMey8um3iimUUyBmtjxMJ4iAzy2yw1WA48DtYANKXYd/t3AEoXtPD42kIogiELoUSNYoOQPu0cptuiU8LikoIQakbDUmX3/Xo818p921yXJ4f0ScI2TJqOsNuqTGDJlz+DQ9ToO10CZYH/+m88NfmMb2HhvUAOcgnDmcTZAQ/urglA4LPMVSj54eN03iDxvzJlEoPLluUVg1zKslplxgJPnDRUjoOR/WDmLHSntEXlqow8wIIQznm9Telm7+KBgT3R3m5cTrkkqlaWvyY5TdC4Jpb5VrT6BkpGf7p7TIGqbVLgfCz9AoNMRSqQ9KJuiGUiX3mnr9ydCXEAMaqn2aYiK0BV75vo2czU3ZQCCrirnufsuBip0qf8v3WOLlXbpkwhC8mjxJI/5YhG652axwFBCtcPGV/LNuSXIJGgUTUrZrRyVRcSa+WJUSZ/hh3R8ZCruXX6BiSw9hSXnmztMwga/WKANeJEDPWkUEXIrfyX/mKAVOxJQo85gPMF1cJe7yaHVGviyavaRBKvQVkn/p9k42N0G4UA9s9PmTOAmmNOfoiUx5tQFt6SnVcZuda11Ly1xvgBIFidTPOKvaX/YbmfH5UOkxUg5T9dKTyuuWLHdXe3mGaDoRGb4TQ/5dhFNY97Vi + EMAIL_TRANSPORT_DEFAULT_PORT: AgBz5kQIjVp6ZZWQ63MJn2lmLOIOf+Z6thh7VKtVGToDodTsuEU6IIQ1FdSA54A/JMiKJ0/Al0QU0y95vZN7q804zHr7V/njxlNWw6ADa1l8ITooMNFDp9ANt3OiKs6QYAYbTKqDTk8HbV/D22pBCU/oh05VMVHWKdRi43/G87RhZhIWFs7TxU0lnn42N8GWbWqf5oHwWa+nXKWE3aclqBQoJBsBK9mLxgf8m4S80sLP0EfqNoVLHTeoktS2BKtKjB3HlTVUUA2xoO+FJDrkcKNrQcHR/0Ely/PhsHdZJNiekTzk97DVvQ6zax2gRJG9eImt6K6Ol1VRCs4mC+PJ7Lfa1B4vnx9CeQDse8B2tSYJnLkmJimgNrYog0X8Sg21LqCMxo3j/Nweor9nkNFUIYDvsql50NoSwxCo+dpMzI67XMZVbKqkchN/0sz+/LBH5e25IgLAbYF5du8gveLUmCVQIou0UsyvxampVC5KmlNndpc+bpBWOf1l2SDau7Y5M8K84Qt4iAuLj9OyObLtf7avpn3awK+isx+OdR6wjB+a+DzgqPkF6J0IpXbLHN/LcmD11inP5WkqhCGb8LnrJv/ZWhu2mFIjAyIeQ2JRcdU369Pjkn5yC6Cb0qov+G/fU1Cz7B4W3V+BwqRy+DEVWBKruxEEjLo0lgKJk3hBrvNAz/svjwA/jOiR3VaAS8fJvgy6hw== + EMAIL_TRANSPORT_DEFAULT_TLS: AgCbrEIXJXb6M+eIqcMrXkgnN1NgIsqawcFq2M8w+7gSU3i2h2sWcaR++1Il9XX/l3+QeIE46sLGvdt0mmkiIMsNtwyw83ATnWDjb0b2dsaCYiOZN1lmhzT1vJNKDUk6GazkxSFAPsSt8NFd+y1QX/ugbso43WPHusVuzQ+6THiDaBCJfQRT3zWsmUffSRbMBhlWPLcJx/cPijKFjhESij7gLzYzk8iKZFWvz1Bt8lGKULH42kC9kzgULQN3/Iwg36/qJk82w5FjbuyW8CQzbWQGgqjfyIhfXakA0+xdGrffpOu/hYY3Vv3YQpZSKLrOo1T5oVcPTB3EkgYMXQ4t4n8KNjg2OL6dW6fX+vHqbsYj1hIm1SQyU0fdkxq1jZZo1JUsktUZaKoh0cT/oMwOl/LBMp7rUNJwZ1uHbQY5dHuv0u61M8Y0ES53IxinfvlMSxHufqlyRAfqEO2eNdMBiDO4/r3Qpv3lxW/ra6twjJzPaVNbp2m4Ce2gx5TdIePeZMERpGDWRmFl3nB/UK1WN+G911C5PhwwV3nEJzDFSrDYGJWKPjDCf+mZQZa8KydkC4YqGCBQPzPh2ESbQmlpU58XFHqwHlaEjSoG4ykRPcwAxU+XvfTehh23/zbWmSkPRG8ofKV2BYWlLTmi5RZ70M8YupKQ3p8vrh2RNbt8552zFUE22DNGRodnmyOuYtP8peRWmQwkew== + EMAIL_TRANSPORT_DEFAULT_USERNAME: AgBMeyur69Y1uv1T5JALcscBb8xm24WYe9PwEghN3iVeXuvguHA2KnDmyad3tY81RM06z/7fotBwQrKROnQrNMXND6cDVRaqQ9p4P17D0Z1LXia8OKbw0W97/K+Z7vJDq1UJS19fV1RYWszqwEIs/Cj6qBwH5gYB1TgA7cv8ub2NIten+/GSugow9fkyinATwsSKANDk256ixWo8VYUAnoSQZHghoPjojtYNQJnYhtsC4tHaQzk77kSr2byTb1iAP8Qs1VIMT8YHJYzZznxVG6O7fQDst9+8ow7PWqDupqlu52jfFkDAi8XGsclAQK0yN0f3g/6maedCF/wfookmcRq+cxC0sAPjy2E0QWB3Jh2s3jiOZtmBqf/rz5iOeKWw/sZ1emtMDNb++Bjy1pqMg/8PaMQdzPjNAXGQiaPBIlklSLSjw2Fs5NY/SLcOPLwiO5m6Dr8U75TJFvCfHn4FH3HpfNRF/xHKYc0z0yPP8wUl7+UeezqQSSYzmyVzkVXLwPuuwnrkMynlCafrSxVmlOG8wZQLsgWL1rrl/jRxQozv/ga/BueKCqlm6lwp4L2C7e2JDTw1ADbsfQ5iy2c7JRzLdwt+Smg+ca6vZ5VQmqplNh6Z9Iqd3YJvGZLH9+DSi1aFTnzv9qYGLDxD0ESEr9uoivVO69lgigIpAMCPcsiCLoRDXwpyJ9bOKLzMzr/YV+XYlB5olK+/vw== + PASSBOLT_JWT_SERVER_EXPONENT: AgBTo8fM7g7pN1/3XG+oazxDVKy28jCOhq8P7VuKTX4CZhrm24hJ5CWbQoa+UZ7nDSjgwGOm1vaVFyMhqO1klax6InjLgNsVtOF6iFBtBwMvkalR99pJMWpgRTXvV2iYrbgK9715XtY6Od3KEYQeMts37k+f0ueejL3l0gOAD0bAPfcXY/wVw/WC6PRZqfZEdkUVyKNY7yznDLXt5Alm05z8OUiyvU20MBnnBlmuxS4oPVsydOxtEtWCDtsSNl8dgqjURA4sYR7nb1QjHrkO+uc3fITFuClHMUwys/o/Fr9OTLR7y3SebSiQgdWEQGHSlSXDJerhJ/PtXza1MSglNgTAN7lcqFrP9zncoj4EDtHykmpoZqBaQNUZrSWfaWDLKepSKN86oeOaqpQnMkA+DFtVJg9Wef4b6D9GjNqYO3oiRNegxjgLhj5ht1/ViuYy6YNAvlOLREUVx1OgRoq1fB7TdhR8ScmvSE1EYWmQuprfwQxBaBk4Kn17vumJVtfDnsfves8fgOlfq0UxiiUEMGz7rdtGOOwDNifqKfjl9e0gGdSaoV/IqTU0SleiX0KKLvgL5NJ/F8NXv3esXyfpzaJz5V8FPeQSrlHzzwZJLLd1R8e9Gmf7Vt3ZAITcMR2vkDWt8KALpkDEv8KOQeF43SxiLauNqBJeW+MRhWbezR5KUdblcSxkRDU34ZlZzWyWmAIU87TW4Rzrbb7axl+pRY0u6FGQ6ox1qT8NgZgwZvMU4IshbHtXxro/zFGmEg== template: metadata: creationTimestamp: null name: passbolt-secret namespace: passbolt type: Opaque +--- diff --git a/apps/passbolt/passbolt-service.yaml b/apps/passbolt/passbolt-service.yaml new file mode 100644 index 0000000..f86c142 --- /dev/null +++ b/apps/passbolt/passbolt-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: passbolt + namespace: passbolt +spec: + ports: + - port: 80 + targetPort: 80 + name: http + - port: 443 + targetPort: 443 + name: https + selector: + app: passbolt diff --git a/apps/platform-prod/paperclip-auth-secret-sealed.yaml b/apps/platform-prod/paperclip-auth-secret-sealed.yaml deleted file mode 100644 index 708c742..0000000 --- a/apps/platform-prod/paperclip-auth-secret-sealed.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: paperclip-auth-secret - namespace: platform-prod -spec: - encryptedData: - better-auth-secret: AgA5HmbWv21FilfH73iNAgqiTtImfz2noM6b5O+WImgMkE+EBTY5EXOD7puZJmuK7QJKNv2+81e7PIpAuZeTlw+3AIp43BebSElpQcGAfvA66B5TpILIB9cR1zgO+taaRIsoMOvPqzXMbT4bU5TN4CNyCavRQx6uMc/MRpJ8/xM9ae+AipcWEY3xPvunKfmJvMowHl2lECjD//V8+LPXNV2KiEoshmPeu2xp3appiLFbm/4pnLoGuiABzXw3PsO49OeQ7n788YyvaCdn81+TzoHF+yeJHyVNMOMLSutO44vQXWdgb86lmWBMbFLgNoGHsD/VWMl3NIYredidxUEQQl6rdpu60Zkl9NTgYtogIkmmt7MaJww8Bkocvggf1gQxYLzbowWFCO50UwNLXy4yYtZbpH/gj+wbL3BnH9Sqn2Rk/QwxDsldMfbcmJk+dvX6s2jGcQ1YCQnKC26g6NZ7xCIcuZtEaSYTJthzYPpTdYYj9SY+1XpAItlnW9dj+zVg3wxifBViB7nqAHXjB3yHztiGyUTQo9C22zIgewxEq/4gCxLYx3HfPyHOKPD6aHMmFnTIFszO2JleV8xjiMy5+m9sFYvxUaPyHY2nO69CqoPJji7f8125YHPPZimUbluyPi2eXDCrN9aRXxe2478pyXaogfWft/vTX1lNyb79k2H/eKL7lpGDzov0smIqTgdUBi6VALpw7n23v3dCEniUwuE/UlcVCYfQQ1bUqw55X5FXUM5aSDS+ps6ZbcIiTUfdRsbopIOrJcukBpF+ - template: - metadata: - creationTimestamp: null - name: paperclip-auth-secret - namespace: platform-prod - type: Opaque diff --git a/apps/platform-prod/paperclip-oidc-secret-sealed.yaml b/apps/platform-prod/paperclip-oidc-secret-sealed.yaml deleted file mode 100644 index e33f89e..0000000 --- a/apps/platform-prod/paperclip-oidc-secret-sealed.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: paperclip-oidc-secret - namespace: platform-prod -spec: - encryptedData: - client-id: AgAW44lRQJw+NgeQYhxFklVOFieFew0Nq/fInttWrf3G81QDXJkclqNAFTk7xMsvDff4N3OpGdPojFwdmng78ZXtvYkgvoR9iu3uIItZlMOZqdtSBsHMOJtls36G2slPA3z4OEmCxW7bnRzrUpV6VbSKCCa8A5AAMZAxHFGUGDrm61wen0U9jIDDn+Xa2AMf3XoHWVZEQ9cBg77ruhNmmw4JssI6rbuDkiFmSKMAmNubel/IjhAtZ2FJvA32Zz2Q0p2DEXSsx8jXdSRtPL0tvZICltojBBL2HXQ8dTZKOQqo+JXzJx6hKbCHPORzirIhCyZSFrZv2ac8qo3y2XzMM9EQVAs3QJHXiNqi9jFD/OV862OoOo8cVe/tk2h3/bj49pR1WPp+Mmdza/U7oxtj+Mns9nO78KJsqQDT22b2vnvTQBY6fzDsmBw2+rEAZx4UlEW9xaApzwv2V7ZermIQuo5AQw+HXN3HKESJyfCZoeMX204qNSHhh88n6O8VTxin1ZB2OomF8+CZrmANluHNNsGPVwFFMl7sr9fdtLlnb2x8ux+oRi29/6K/x4mYgk2lVg+VHD58NV9HFFb08kq6rSP71WJE1eCZSix8HIL1Io6ahUDfNQ0sHk/OHni/BwY7S7cYgVGv2n6nyy5uJ8oT5/ce2vE4TXABdkNonbM/igA3nIsh6YC4oiGz2Ck2mehixxvn+7TpluhWodJvTExvw2I6tntE1dmXG1Q6FrHyO17quY17Ah8= - client-secret: AgB73qCz4Rp0ZasaqZ6kbROSIuo0Dld7WStDV40Kel3pfTeFhvTALVy3Lp1OjYHzUZ6v2AeolLH0ewmEEyXOdDCspgXfURxXAgoCi6oFYrfeXJIvVlvbnPyVvAbFaVmyFGtVrQyFi+tbhd77YMxquA2fYobCWc3NHIDf9EnLEgfLvdjoWhk3BsCBzFmqgbxsOAPkkhcocqcTff4DntUY/FnsHVRCESid3r+xZsa+Bu3wTSJZH/CyOfRss8vaieBVW3DDglV2/GCCN7TUmdaZuLx3SwAw/zDkOHThkBz59SieVuJf1LP/ax8euMZ+dVLWQ+dc0lDBx+RdzV/hILny+7NzqoegCkRcqapBRgDxQygu1NHwbgkW6jWASaMrrP0VmBus1vQezkXeYoJK+ECrD2WBZ+WlW+9k7yqwZFW3xeC9BJ5N8HTdjivzatQ66m0ovSyTyYGyBYfJ+DUNiD30hRUHMRKmynpLs4dCFCFvWT9vXt87WeSKLKngSB285Q39URKOk9eJZL7Ob8lBCzAZ0rlnQKeUO5Zo1hdVFvuDAQIh0ELiKMMlGDJZEs4qU2yPk26tskZEHw4Wez7v8ANK4HTBUKalqJ0uio3xeEg/xZw6zrZH5tG3tiMGVGScfLaSErFVwZh3j2wSOhrOaqa/3FwhRmWjnl6Ol6Dr6OU1dO/8B7dhU3AP8d5tQMsyApJ2J7kUDNtqxvhqrMe5cp3p313Wj/XzMzFtloEU/s0gBmpoLw== - issuer: AgBoZ2oQ/RvqabCnuUQP5cDtBHJdx9nDUlEMp2GSDJ/T5jnB1fuBe5dM/031GBR0378udqmiv7KCCq9eSIlYGQoKomvWQwEa4nEamQktu9xzYxGUzwOK6oH55Tpe2OMXSC0BG5Wx7mZX9JMA/ZBkqDuE/XynLO1P3VedePZwSP6EKkaGqQ1csqmVivYoPh1kMGN/MI9c/IwhdqM8NQOK7jXwZKHWI0oLl9eUJh9d73w/f3EVslMNYFnhBPPN7lD4TtwJRk/iQueeFMaazObnY4LDlzjI28v3aFmptie5QMVBRXcGcotIs5NS04JOkwuxp8uPBDRTYGenWglYzab5Ep5dZywxF+4SQbUETxcki4UIdahFe1F2t3NlIuuzVQy0ltDkWCueepPybv8tuYY36IpZfHVFNYuCa9snMDlPQWs2/EXkxwFkVldxhhXXcGIimE+UI3t685xTvhiGVwIOSPIK/41CvprZWdMpw5iWeF2V/2RPKhfE2Pa9ARPgX6dZ4+LPO40HOHaDfhhlBIj+UbUq3pjRsLzRB7gij0bbkejTq+jQGefEjzTkO6vGvcRKAuWc5bSO0TBKQL3ItgBKKLhqcAjjXo9T0XXhennL2PDdLQLl9ixLzFkTLSlRXMRTRtk3a0hVI7vXY5POwnxuyqKEKhNTaqis9yi+bm0wAtza4/uw0ZdjFNmV7b47KSUFmlzdze8CHgLEkhk6x3i0eBUb0av+NLS8oz4pYg== - template: - metadata: - creationTimestamp: null - name: paperclip-oidc-secret - namespace: platform-prod - type: Opaque diff --git a/apps/platform-prod/paperclip-session-secret-sealed.yaml b/apps/platform-prod/paperclip-session-secret-sealed.yaml deleted file mode 100644 index eb7ce00..0000000 --- a/apps/platform-prod/paperclip-session-secret-sealed.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -apiVersion: bitnami.com/v1alpha1 -kind: SealedSecret -metadata: - creationTimestamp: null - name: paperclip-session-secret - namespace: platform-prod -spec: - encryptedData: - secret: AgCQntkVAWf6xX1Gv1f1Wx7NVc42nUMSTktjeI8xv1t4Uia4Sk6q1YQWFfoksxT4zlQJlR+ObctV97+h4AXiKYuVYXfF3CkQzdG5BAvOvhowo7LtUHkh+k3m9j/8MuO2l1ud1QKPNYV2Bi/vJ+x4ifX31l8QZexitVusb31KMWc1wDoRuIo4Ntk9BCIuEA9ctg7XbG1t17pMGMHGasL0vYb2fN/GePFKhaFoe00ZThZBAI2er4pVM5AV6Q33xQTXTAN5VWJKu65/NeGi+xeUXs0/I7oOesHKf0G51zjUT2I01ZtaFTmX0hzBuKCRA/SNyq5IT0+V46euItgD/vg8HAj8J5yVyvoaAtg9Ts44QsPOmkuXGpCsCj4PmhE8ZZVXarH8t5qmdWXwbgR9sR8+3mUAP2pnq8D0CNLowLmxE9HVPuUxWIsCpBHAc6f9eH8jclT+H/KmaW7wcaIIuMB3BH3Si6ZanCBKpy0oz0qKllHQt+l1W7nA8W4hXR1h2aVOOThzUbtg/j+xAmJz+pfQx9KUaucLZxa3/ZdQFtVOU8dF6slQcIrt+OB8XXebTk/LGJDgO27sGKjY46kRFkIIJ6U/SLvsyfrZNTHxMqSariQQosekqkNZ+ME3wRE5D8UgQtRfkK44KwyU/zKjXCDgXBIAI9gNJRs77yAzPeQv4AoKGVTQ60Li2bA2PMoPjYHY9gGtYNssa/XEQISoEw5dYzRGJOLbtgjA1ZLUrR5nIWUHn5ntff84e0SsumI7AQ== - template: - metadata: - creationTimestamp: null - name: paperclip-session-secret - namespace: platform-prod - type: Opaque diff --git a/apps/pocket-id/deployment.yaml b/apps/pocket-id/deployment.yaml index a530fc0..8a28d2a 100644 --- a/apps/pocket-id/deployment.yaml +++ b/apps/pocket-id/deployment.yaml @@ -17,7 +17,7 @@ spec: spec: containers: - name: pocket-id - image: ghcr.io/pocket-id/pocket-id:v2.11.0 + image: ghcr.io/pocket-id/pocket-id:v2.14.0 imagePullPolicy: IfNotPresent ports: - containerPort: 1411 diff --git a/apps/stalwart/CONFIGMAP-HARDENING-TEST-2026-08-01.md b/apps/stalwart/CONFIGMAP-HARDENING-TEST-2026-08-01.md new file mode 100644 index 0000000..68b3e6c --- /dev/null +++ b/apps/stalwart/CONFIGMAP-HARDENING-TEST-2026-08-01.md @@ -0,0 +1,100 @@ +# Stalwart ConfigMap Hardening Test Results + +**Date**: 2026-08-01 +**Issue**: DEV-431 +**Engineer**: CTO Agent + +## Summary + +Successfully hardened Stalwart ConfigMap mount to eliminate CrashLoopBackOff risk from subPath mounting race condition. + +## Changes Deployed + +**Commit**: 291feb96a39c8f28690ab73d742bb5d94ce65cfc + +### Before (Problematic) +```yaml +volumeMounts: +- name: bootstrap-config + mountPath: /etc/stalwart/config.json + subPath: config.json # ← Race condition risk +``` + +### After (Hardened) +```yaml +initContainers: +- name: copy-config + image: busybox:latest + command: + - sh + - -c + - | + cp /tmp/bootstrap-config/* /etc/stalwart/ && \ + echo "Config files copied successfully:" && \ + ls -la /etc/stalwart/ + volumeMounts: + - name: bootstrap-config-source + mountPath: /tmp/bootstrap-config + readOnly: true + - name: config + mountPath: /etc/stalwart + +containers: +- name: stalwart + volumeMounts: + - name: config # ← emptyDir populated by init-container + mountPath: /etc/stalwart +``` + +## Test Results + +### 1. ConfigMap Hardening Verification ✅ + +**Pod Deletion Test (3 iterations)** + +| Test | Recovery Time | Restart Count | Result | +|------|---------------|---------------|--------| +| 1 | 69s | 0 | ✅ PASS | +| 2 | ~80s | 0 | ✅ PASS | +| 3 | 93s | 0 | ✅ PASS | + +**Conclusion**: No CrashLoopBackOff observed. ConfigMap files are consistently populated before Stalwart starts. + +### 2. Alert Delivery Test ⚠️ + +**Timeline**: +- 10:06:52 UTC: Pod terminated (scaled to 0) +- 10:08:52 UTC: Expected StalwartDown alert (2m threshold) +- 10:11:52 UTC: Expected StalwartExternalProbeFailure (5m threshold) + +**Finding**: Alertmanager is configured to route ALL alerts to "null" receiver. + +```yaml +route: + receiver: "null" +receivers: +- name: "null" +``` + +**Implication**: +- Alerts WILL fire in Prometheus when conditions are met +- Alerts WILL appear in Alertmanager UI +- Alerts will NOT be delivered to any external notification channel (no Slack, email, webhook, etc. configured) + +**Status**: Can verify alert firing, but NOT external delivery (no notification channel exists). + +### 3. Mail Service Health ⏳ + +Verification pending after alert test completes and service is scaled back to 1 replica. + +## Artifacts + +- Deployment manifest: `apps/stalwart/stalwart-fresh-deployment.yaml` +- Git commit: 291feb96a39c8f28690ab73d742bb5d94ce65cfc +- ArgoCD app: `stalwart` (namespace: argocd) + +## Follow-up Recommendations + +1. **Notification Channel**: Configure Alertmanager receiver for production alerting (Slack, email, PagerDuty, etc.) +2. **Alert Test**: Once notification channel is configured, re-run scale-to-0 test to verify end-to-end delivery +3. **Documentation**: Update STABILITY-CHECK.md with corrected alert delivery test procedure (accounting for ArgoCD auto-heal) diff --git a/apps/stalwart/README.md b/apps/stalwart/README.md index 3df5d19..a8e5290 100644 --- a/apps/stalwart/README.md +++ b/apps/stalwart/README.md @@ -11,6 +11,30 @@ Clean deployment of Stalwart mail server with username/password authentication o - **Backup**: Daily restic backup to S3 at 3 AM - **Web UI**: https://mail.basicstack.de +## Network Architecture + +Stalwart uses a two-tier load balancing setup: + +1. **Kubernetes LoadBalancer Services**: Four separate LoadBalancer services expose SMTP (25, 587, 465) and IMAP (143, 993) ports. The service type `LoadBalancer` is required because the Hetzner Cloud Controller Manager (CCM) automatically provisions and manages the Hetzner Load Balancer when it detects this service type. + +2. **Hetzner Load Balancer**: Automatically managed by the Hetzner CCM. The load balancer distributes traffic across: + - k3s-cp-1 (control plane node) + - k3s-worker-1 through k3s-worker-5 (worker nodes) + +### Traffic Flow + +``` +External Client → Hetzner Load Balancer → NodePort → K8s Service → Stalwart Pod +``` + +The Hetzner CCM watches for LoadBalancer-type services and automatically: +- Creates/updates the Hetzner LB configuration +- Configures health checks +- Maps service ports to node ports +- Manages target nodes + +**Important**: Changing the service type from `LoadBalancer` to `NodePort` would break the automatic Hetzner LB management. The current setup is the correct configuration for our infrastructure. + ## Files - `stalwart-fresh-deployment.yaml` - Main deployment manifest @@ -41,6 +65,8 @@ After deployment, log in at https://mail.basicstack.de with: ## Configuration +> **IMPORTANT:** The configuration is stored in the RocksDB and cannot be overwritten by a configuration file! + All configuration is done via the web UI or API. The bootstrap config only points to the RocksDB database location. NO config.toml files are used. Check https://stalw.art/docs/ref/ for configuration possibilities. @@ -48,10 +74,54 @@ The API access via /jmap seems to be too complex for the agent and the configrua Refer to [manual configuration steps](manual_config_steps.md) +## Network Architecture + +### Load Balancing Setup + +Stalwart uses a two-tier load balancing architecture: + +1. **Hetzner Cloud Load Balancer** (External Layer) + - Provides the public-facing IP for mail services + - Configured with k3s-cp-1 and all worker nodes as targets + - Forwards traffic to Kubernetes NodePorts on the cluster nodes + +2. **Kubernetes LoadBalancer Services** (Internal Layer) + - Service type: LoadBalancer (managed by k3s ServiceLB) + - Automatically configured by Hetzner CCM (Cloud Controller Manager) + - Creates NodePorts that the Hetzner LB targets + +### Traffic Flow + +``` +External Mail Client + ↓ +Hetzner Load Balancer (public IP) + ↓ +k3s Node NodePort (automatically assigned) + ↓ +Kubernetes LoadBalancer Service (stalwart-smtp / stalwart-imap) + ↓ +Stalwart Pod +``` + +### Why LoadBalancer Service Type is Required + +The Kubernetes services MUST remain type `LoadBalancer` because: +- The Hetzner CCM automatically manages the Hetzner Load Balancer configuration +- When a service is type LoadBalancer, the CCM creates/updates the Hetzner LB targets +- Changing to NodePort would break this automatic management +- Manual Hetzner LB configuration would be required and error-prone + +### Services + +- `stalwart-smtp` (LoadBalancer): Ports 25, 587 +- `stalwart-imap` (LoadBalancer): Port 993 +- `stalwart-http` (ClusterIP): Port 8080 (web UI via Traefik Ingress) + ## Ports -- SMTP: 25, 587, 465 -- IMAP: 143, 993 +- SMTP: 25, 587 +- IMAPS: 993 (secure only, port 143 disabled per DEV-359) - HTTP: 8080 (web UI) ## Storage diff --git a/apps/stalwart/STABILITY-CHECK.md b/apps/stalwart/STABILITY-CHECK.md index e556b3a..382692e 100644 --- a/apps/stalwart/STABILITY-CHECK.md +++ b/apps/stalwart/STABILITY-CHECK.md @@ -71,14 +71,20 @@ This repository (`https://forgejo.basicstack.de/basicstack/stack.basicstack.de`) ### Investigation Results (2026-07-13) -**Infrastructure Status**: ✅ ALL VERIFIED WORKING +**Infrastructure Status**: ✅ ALL VERIFIED WORKING (as of 21:00 UTC) - SMTP (ports 25, 587, 465): Responding with correct ESMTP greeting - IMAP (ports 143, 993): Responding with correct IMAP greeting - LoadBalancer external IP: 178.105.17.239 (correct) -- Pod status: Running, 0 restarts +- Pod status: Running, started at 19:26 UTC +- Service configuration: Correct port mappings and selectors verified - Monitoring: PrometheusRules and ServiceMonitors in place -**User-Reported SMTP Issue**: Client configuration problem (Thunderbird connecting to port 143 instead of 587), not infrastructure issue +**SMTP Issue Timeline (2026-07-13)**: +- 20:16 UTC: LoadBalancer IP updates visible in events (10.42.1.1 → 178.105.17.239) +- 20:31 UTC: User reported SMTP port returning IMAP greeting (Thunderbird error) +- 21:00 UTC: External port tests confirm all ports working correctly with proper greetings +- **Assessment**: Transient issue during LoadBalancer IP transition, now resolved +- **Action Required**: User to retest SMTP sending with fresh client connection ### Architecture Overview diff --git a/apps/stalwart/STABLE-ROUTING-SOLUTION.md b/apps/stalwart/STABLE-ROUTING-SOLUTION.md new file mode 100644 index 0000000..e6d5900 --- /dev/null +++ b/apps/stalwart/STABLE-ROUTING-SOLUTION.md @@ -0,0 +1,150 @@ +# Stalwart Mail - Stable Routing Solution + +**Created**: 2026-07-22 +**Issue**: DEV-359 +**Author**: CTO Agent + +## Problem Statement + +k3s ServiceLB (svclb) with hostPort bindings can leave orphaned CNI-HOSTPORT iptables rules when pods restart, causing intermittent mail service outages. + +### Root Cause + +1. **Architecture**: k3s ServiceLB uses DaemonSet pods with hostPort to expose LoadBalancer services +2. **CNI Plugin**: hostPort creates CNI-HOSTPORT iptables DNAT rules +3. **Failure Mode**: When svclb pods restart, the CNI plugin may fail to clean up old DNAT rules +4. **Impact**: External traffic gets DNATed to old (non-existent) pod IPs, causing connection timeouts + +### Historical Issues + +- **2026-07-20**: Stalwart pod restart left orphaned rules pointing to old svclb pod IPs +- **2026-07-22**: Orphaned rules caused complete IMAP/SMTP outage +- **Recovery**: Required manual deletion of orphaned rules + svclb pod recreation + +## Recommended Long-Term Solutions + +### Option 1: Migrate to Hetzner Cloud Load Balancer (RECOMMENDED) + +**Architecture**: +``` +External Traffic → Hetzner LB11 (public IP) + ↓ +k3s worker nodes (private IPs via TargetGroup) + ↓ +Stalwart LoadBalancer service (standard k8s) + ↓ +Stalwart pod +``` + +**Benefits**: +- True external load balancing with HA +- No CNI-HOSTPORT complexity +- Automatic health checks +- SSL termination option +- Multi-node distribution +- No manual iptables management + +**Cost**: ~€8.91/month (LB11 - 5 services) + +**Implementation**: See `/hetzner-lb-migration-plan.md` in workspace root + +**Status**: Plan approved, awaiting implementation (tracked in DEV-357) + +### Option 2: Traefik TCP IngressRoute (ALTERNATIVE) + +**Architecture**: +``` +External Traffic → Traefik (ports 25, 587, 993) + ↓ (TCP routing via IngressRouteTCP) +Stalwart ClusterIP service + ↓ +Stalwart pod +``` + +**Benefits**: +- No additional infrastructure +- Traefik already deployed +- Unified ingress for HTTP + TCP +- No CNI-HOSTPORT dependencies + +**Drawbacks**: +- Requires Traefik configuration changes +- Must expose additional ports on Traefik service +- Single point of failure (unless Traefik is HA) + +**Implementation Steps**: +1. Add SMTP/IMAP entryPoints to Traefik deployment args +2. Add ports 25, 587, 993 to Traefik LoadBalancer service +3. Create IngressRouteTCP resources (already exist in cluster) +4. Change Stalwart services from LoadBalancer to ClusterIP + +**Status**: Not implemented, IngressRouteTCP resources exist but Traefik entryPoints missing + +### Option 3: MetalLB (NOT RECOMMENDED) + +Installing MetalLB to provide real LoadBalancer IPs. + +**Drawbacks**: +- Additional complexity +- Potential conflicts with k3s ServiceLB +- Still uses node IPs (no true external LB) +- Requires IP pool management + +**Status**: Rejected - Hetzner LB or Traefik are better options + +## Current State (2026-07-22) + +**Active Solution**: k3s ServiceLB with hostPort bindings + +**Configuration**: +- Service type: LoadBalancer with `externalTrafficPolicy: Local` +- svclb DaemonSet pods on all nodes +- CNI-HOSTPORT iptables rules for port forwarding +- Exposed ports: SMTP (25, 587), IMAPS (993) +- Removed insecure ports: SMTPS (465), IMAP (143) + +**Known Issues**: +- CNI-HOSTPORT rules can become orphaned on pod restart +- Requires manual intervention to clean up orphaned rules +- Not truly multi-node load balanced + +**Recovery Procedure** (if orphaned rules occur): +```bash +# 1. Delete all svclb pods to trigger CNI cleanup +kubectl delete pods -n kube-system -l 'svccontroller.k3s.cattle.io/svcname=stalwart-smtp' +kubectl delete pods -n kube-system -l 'svccontroller.k3s.cattle.io/svcname=stalwart-imap' + +# 2. Wait for pods to recreate (CNI will create new hostPort rules) +kubectl wait --for=condition=ready pods -n kube-system -l 'svccontroller.k3s.cattle.io/svcname=stalwart-smtp' --timeout=60s + +# 3. Verify CNI-HOSTPORT rules on each node +for node in k3s-cp-1 k3s-worker-1 k3s-worker-2 k3s-worker-3; do + ssh root@$node "iptables-legacy -t nat -L CNI-HOSTPORT-DNAT -n" +done + +# 4. Test external connectivity +telnet mail.basicstack.de 587 +openssl s_client -connect mail.basicstack.de:993 +``` + +## Recommendation + +**Implement Option 1 (Hetzner Load Balancer)** for the following reasons: + +1. **Stability**: Eliminates CNI-HOSTPORT complexity entirely +2. **Scalability**: True external load balancing across multiple nodes +3. **Reliability**: Hetzner-managed infrastructure with automatic health checks +4. **Cost-effective**: €8.91/month is acceptable for a production mail service +5. **Future-proof**: Enables future HA Stalwart deployments + +**Timeline**: Can be implemented in ~1 day (see DEV-357 migration plan) + +**Fallback**: If budget/approval is blocked, implement Option 2 (Traefik TCP) as interim solution + +--- + +## Related + +- Migration Plan: `/hetzner-lb-migration-plan.md` +- Implementation Task: DEV-357 +- Historical Investigation: `apps/stalwart/ISSUE-2026-07-13-smtp-imap-external-access.md` diff --git a/apps/stalwart/stalwart-admin-credentials-sealed.yaml b/apps/stalwart/stalwart-admin-credentials-sealed.yaml index 17dd544..b134acf 100644 --- a/apps/stalwart/stalwart-admin-credentials-sealed.yaml +++ b/apps/stalwart/stalwart-admin-credentials-sealed.yaml @@ -16,6 +16,8 @@ spec: recovery-admin-password: AgB3QBYlyS7D71xR0ohvyxmfUIvgplaGjEj5RAqEY5fvKcksNjA8i3FEY3WZzKcxymG9L1ePsmxgUJUZNYbwnO9TwkaeDfEuPHlWiG271XXrQrYDl4ftCGKlRbMuY3DBZSwMB5OvC+1+PEKCxeN5J5eO2VjjFp6eUBgD4RzBFFvQV4D9kwWIJ1IIG/PidLJhf78PPoSUK46FXGuQlBKozjefaofIycWowDSFtTdotJyXbIr51iwNOyTsq5rcd1Z7EEGwrVpKYGpXJulsILImp1YJ4PUMowTwFWaj4R5/nEoteVenJM3o6a9CY7c31VDObDouAhrCa/k/onAmVrxFFAMKTHLtfORQMcOSR8vyioB41WMgBzGa4MyqdgnUSV91aYANCx1TveGqcROtMUo1WQwk3LzJNHPtxp/0ZrfDWDS1kgznajs12GRmfWI7NB90GbsirfcZedgg4FkdeSAFgL7Nb3BhoK06Fp794w8FlEGJATyoS1BdB+6aai7TPhLL8quN9jQuA8cVHI8vVjF+vDUAUtIad7XXhcGVs6E//fdY4I8dVX1L8TjpvrYZFkXOZzVOi6LCLgFbNf1IStYd8AX22RTZs1ny9EDyBXRdawwpXCm4y07e5P/4/vXVOTtmA11uzzzeZ4kfdbv78alJ4WzZO17lG/jX3aakOHL99MH2GZt/1lvHMpfY8UV4lM2u9T9zfJ4lvT2g6fCLlET6ivMubskwiOunYKiAcSQG8NqFiJDhZN/OF2zSApNqfQ== template: metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous creationTimestamp: null name: stalwart-admin-credentials namespace: stalwart diff --git a/apps/stalwart/stalwart-bootstrap-config.yaml b/apps/stalwart/stalwart-bootstrap-config.yaml new file mode 100644 index 0000000..17e5ee2 --- /dev/null +++ b/apps/stalwart/stalwart-bootstrap-config.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stalwart-bootstrap-config + namespace: stalwart +data: + # DEV-476: Bootstrap now points at the PostgreSQL config/data store. + # PGPASSWORD is injected into the stalwart container from + # secret/stalwart-postgres-credentials (see stalwart-fresh-deployment.yaml). + config.json: '{"@type":"PostgreSql","host":"stalwart-postgres","port":5432,"database":"stalwart","authUsername":"stalwart","authSecret":{"@type":"EnvironmentVariable","variableName":"PGPASSWORD"},"useTls":false}' diff --git a/apps/stalwart/stalwart-config.yaml b/apps/stalwart/stalwart-config.yaml new file mode 100644 index 0000000..c9da9bc --- /dev/null +++ b/apps/stalwart/stalwart-config.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: stalwart-config + namespace: stalwart +data: + stalwart.toml: | + # + # Stalwart Mail Server Configuration + # + + # DEV-476: primary store is PostgreSQL (see stalwart-bootstrap-config.yaml). + # Note: the container starts with `--config /etc/stalwart/config.json`, so + # this stalwart.toml is not read at runtime for a live pod — the bootstrap + # JSON is authoritative. Kept in sync here as documentation and for any + # one-shot tooling that references the toml. + [store] + data = "postgres" + + [store.postgres] + type = "postgresql" + host = "stalwart-postgres" + port = 5432 + database = "stalwart" + user = "stalwart" + password = "%{env:PGPASSWORD}%" + tls.enable = false + + # + # Server Configuration + # + + [server] + hostname = "mail.basicstack.de" + + # HTTP Listener (Web UI and API) + [server.listener.http] + bind = ["0.0.0.0:8080"] + protocol = "http" + + # Security: Allow internal cluster IPs for Traefik ingress + # Pod network CIDR: 10.244.0.0/16 + [server.listener.http.security] + allowed-ips = ["10.244.0.0/16", "127.0.0.1/32"] + + # SMTP Listener (Port 25) + [server.listener.smtp] + bind = ["0.0.0.0:25"] + protocol = "smtp" + + # Security: Allow internal cluster IPs for Traefik ingress + [server.listener.smtp.security] + allowed-ips = ["10.244.0.0/16", "127.0.0.1/32"] + + # Submission Listener (Port 587 with STARTTLS) + [server.listener.submission] + bind = ["0.0.0.0:587"] + protocol = "smtp" + + # Security: Allow internal cluster IPs for Traefik ingress + [server.listener.submission.security] + allowed-ips = ["10.244.0.0/16", "127.0.0.1/32"] + + # IMAPS Listener (Port 993 with TLS) + [server.listener.imaps] + bind = ["0.0.0.0:993"] + protocol = "imap" + tls.implicit = true + + # Security: Allow internal cluster IPs for Traefik ingress + [server.listener.imaps.security] + allowed-ips = ["10.244.0.0/16", "127.0.0.1/32"] + + # + # TLS Configuration + # + + [server.tls] + certificate = "file:///etc/stalwart/certs/tls.crt" + private-key = "file:///etc/stalwart/certs/tls.key" + + # + # Logging + # + + [tracing.level] + default = "info" diff --git a/apps/stalwart/stalwart-fresh-deployment.yaml b/apps/stalwart/stalwart-fresh-deployment.yaml index 8f9dc6f..384ac11 100644 --- a/apps/stalwart/stalwart-fresh-deployment.yaml +++ b/apps/stalwart/stalwart-fresh-deployment.yaml @@ -24,7 +24,7 @@ metadata: namespace: stalwart spec: type: LoadBalancer - externalTrafficPolicy: Local + externalTrafficPolicy: Cluster selector: app: stalwart ports: @@ -36,10 +36,7 @@ spec: port: 587 targetPort: 587 protocol: TCP - - name: submissions - port: 465 - targetPort: 465 - protocol: TCP + # Port 465 (SMTPS) removed per DEV-359 approval - enforce secure protocols --- apiVersion: v1 kind: Service @@ -48,14 +45,11 @@ metadata: namespace: stalwart spec: type: LoadBalancer - externalTrafficPolicy: Local + externalTrafficPolicy: Cluster selector: app: stalwart ports: - - name: imap - port: 143 - targetPort: 143 - protocol: TCP + # Port 143 (IMAP) removed per DEV-359 approval - enforce secure protocols - name: imaps port: 993 targetPort: 993 @@ -97,6 +91,29 @@ spec: app: stalwart spec: initContainers: + # Copy ConfigMap files to emptyDir to avoid subPath mounting race + - name: copy-config + image: busybox:latest + command: + - sh + - -c + - | + set -e + echo "Copying bootstrap config..." + cp /tmp/bootstrap-config/* /etc/stalwart/ + echo "Copying stalwart.toml..." + cp /tmp/stalwart-config/stalwart.toml /etc/stalwart/stalwart.toml + echo "Config files copied successfully:" + ls -la /etc/stalwart/ + volumeMounts: + - name: bootstrap-config-source + mountPath: /tmp/bootstrap-config + readOnly: true + - name: stalwart-config-source + mountPath: /tmp/stalwart-config + readOnly: true + - name: config + mountPath: /etc/stalwart - name: fix-permissions image: busybox:latest command: ["sh", "-c", "chown -R 2000:2000 /var/lib/stalwart && chmod -R 755 /var/lib/stalwart"] @@ -105,37 +122,40 @@ spec: mountPath: /var/lib/stalwart containers: - name: stalwart - image: stalwartlabs/stalwart:v0.16.11 + image: stalwartlabs/stalwart:v0.16.18 ports: - containerPort: 25 name: smtp - containerPort: 587 name: submission - - containerPort: 465 - name: submissions - - containerPort: 143 - name: imap + # Port 465 (SMTPS) removed - enforce STARTTLS on 587 + # Port 143 (IMAP) removed - enforce TLS on 993 - containerPort: 993 name: imaps - containerPort: 8080 name: http env: - - name: STALWART_RECOVERY_ADMIN - valueFrom: - secretKeyRef: - name: stalwart-admin-credentials - key: admin-password - optional: false + #- name: STALWART_RECOVERY_ADMIN + # valueFrom: + # secretKeyRef: + # name: stalwart-admin-credentials + # key: admin-password + # optional: false - name: TLS_CERTIFICATE value: "/etc/stalwart/certs/tls.crt" - name: TLS_PRIVATE_KEY value: "/etc/stalwart/certs/tls.key" + # DEV-476: bootstrap config.json resolves authSecret via this env var. + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: stalwart-postgres-credentials + key: POSTGRES_PASSWORD volumeMounts: - name: data mountPath: /var/lib/stalwart - - name: bootstrap-config - mountPath: /etc/stalwart/config.json - subPath: config.json + - name: config + mountPath: /etc/stalwart - name: tls-certs mountPath: /etc/stalwart/certs readOnly: true @@ -147,25 +167,37 @@ spec: memory: "2Gi" cpu: "2000m" startupProbe: - httpGet: - path: / - port: 8080 + exec: + command: + - curl + - -f + - --max-time + - "3" + - http://localhost:8080/healthz/live initialDelaySeconds: 60 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6 livenessProbe: - httpGet: - path: / - port: 8080 + exec: + command: + - curl + - -f + - --max-time + - "3" + - http://localhost:8080/healthz/live initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: - httpGet: - path: / - port: 8080 + exec: + command: + - curl + - -f + - --max-time + - "3" + - http://localhost:8080/healthz/ready initialDelaySeconds: 10 periodSeconds: 5 timeoutSeconds: 3 @@ -174,9 +206,16 @@ spec: - name: data persistentVolumeClaim: claimName: stalwart-data - - name: bootstrap-config + # emptyDir for config files (populated by copy-config init-container) + - name: config + emptyDir: {} + # ConfigMap sources for init-container (no longer mounted directly via subPath) + - name: bootstrap-config-source configMap: name: stalwart-bootstrap-config + - name: stalwart-config-source + configMap: + name: stalwart-config - name: tls-certs secret: secretName: stalwart-tls @@ -256,7 +295,7 @@ spec: jobTemplate: spec: backoffLimit: 2 - activeDeadlineSeconds: 600 # 10 minute timeout + activeDeadlineSeconds: 1800 # 30 minute timeout (was 600s; retries + affinity settling can push past 10 min) template: metadata: labels: @@ -264,9 +303,20 @@ spec: spec: serviceAccountName: stalwart-backup restartPolicy: OnFailure + # Co-locate with stalwart-0 so both pods share the same hcloud block volume + # attachment (RWO). Without this the backup pod can be scheduled on a + # different node and hits FailedAttachVolume/Multi-Attach (DEV-468). + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: stalwart + statefulset.kubernetes.io/pod-name: stalwart-0 + topologyKey: kubernetes.io/hostname containers: - name: backup - image: alpine:3.19 + image: alpine:3.24 command: - /bin/sh - -c diff --git a/apps/stalwart/stalwart-postgres-backup.yaml b/apps/stalwart/stalwart-postgres-backup.yaml new file mode 100644 index 0000000..1a4634c --- /dev/null +++ b/apps/stalwart/stalwart-postgres-backup.yaml @@ -0,0 +1,120 @@ +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: stalwart-postgres-backup + namespace: stalwart + labels: + app: stalwart-postgres-backup +spec: + schedule: "30 2 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 2 + activeDeadlineSeconds: 1800 + template: + metadata: + labels: + app: stalwart-postgres-backup + spec: + restartPolicy: OnFailure + containers: + - name: backup + image: postgres:16-alpine + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - | + set -euo pipefail + BACKUP_START=$(date +%s) + echo "=== Starting Stalwart Postgres backup at $(date -u) ===" + + # pg_dump already in image; restic is small and only needed here. + apk add --no-cache restic + + export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/stalwart" + echo "Repository: ${RESTIC_REPOSITORY}" + + # Repo is shared with the RocksDB backup, so it already exists; + # init only if the RocksDB side has not created it yet. + restic snapshots > /dev/null 2>&1 || restic init + + echo "Streaming pg_dump | gzip -> restic (tag stalwart-postgres)..." + PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump \ + -h stalwart-postgres.stalwart.svc.cluster.local \ + -U "${POSTGRES_USER}" \ + -d "${POSTGRES_DB}" \ + --no-owner --no-acl \ + | gzip -c \ + | restic backup \ + --stdin \ + --stdin-filename stalwart-postgres.sql.gz \ + --tag stalwart-postgres \ + --tag daily \ + --host stalwart-postgres-k8s + + echo "Forget/prune (tag=stalwart-postgres, independent retention)..." + restic forget \ + --tag stalwart-postgres \ + --keep-daily 14 \ + --keep-weekly 8 \ + --keep-monthly 6 \ + --prune + + echo "Latest stalwart-postgres snapshots:" + restic snapshots --tag stalwart-postgres --compact || true + + BACKUP_END=$(date +%s) + echo "=== Postgres backup complete in $((BACKUP_END - BACKUP_START))s at $(date -u) ===" + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: stalwart-postgres-credentials + key: POSTGRES_USER + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: stalwart-postgres-credentials + key: POSTGRES_DB + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: stalwart-postgres-credentials + key: POSTGRES_PASSWORD + - name: RESTIC_PASSWORD + valueFrom: + secretKeyRef: + name: stalwart-s3-backup + key: restic-password + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: stalwart-s3-backup + key: access-key + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: stalwart-s3-backup + key: secret-key + - name: S3_ENDPOINT + valueFrom: + secretKeyRef: + name: stalwart-s3-backup + key: endpoint + - name: S3_BUCKET + valueFrom: + secretKeyRef: + name: stalwart-s3-backup + key: bucket + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi diff --git a/apps/stalwart/stalwart-postgres-credentials-sealed.yaml b/apps/stalwart/stalwart-postgres-credentials-sealed.yaml new file mode 100644 index 0000000..80fcfe3 --- /dev/null +++ b/apps/stalwart/stalwart-postgres-credentials-sealed.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: stalwart-postgres-credentials + namespace: stalwart +spec: + encryptedData: + POSTGRES_DB: AgBl0xkIxrdRjAlv0JP/rPXwV4sbfFcLc1TwPKZ0VC7+MXd9K1s+FMEottjxe3jUMeVk1ddI0OelZ7PlZcVeocvC4fq02djtagLcjSBn5AFqnn0vJO+sAKsnqxuyQv7IIuNhqL5a88obOeuKJPB325IsXLEhFUrCjXDz4KTjn1RW4p9sWcbn1jBbGYHSRpp7GMn6LgkJOjhbpiQeptr270dTiOjHAxI3Ky507v5qAA8m5VwjHK9pXN8QkkYqIq3mXp4WWRt9LVAfnaQ+tnxPpeZBgzf3pV2Hj4yDpFuans8xGft9e5Er2ygEwpUD+AOEJdjSntzvkx4K5Lprv3ud1PXY+f5LZb7w41Ex2fwQtGKWepQyAD1LSqtapkOVirgWOfmGxR0NcyWTpqu6VuVYPgUq+I37CCmTdVKbBmqhSIMRll0E7Cnzse1wiLIugyHtidg5whWpYiHiByEKCxwmd0LKAGZ2gTrIOctRmpLb5UYvDxr+T/mrG20p4SymFucaT7MrvMXRnewB5HSvhooXgFyTq7zNL6mlVgK5oQI1RRJ71dNtrjs2Z/RLCzffbV8K3oSM8lFuN0BBea5M8QorJoraqwa7cHPyCCCbuJuXBMo8ZDC96etvoICiBFwt4hFcxuOS43XX6CLZiVq516/TN4VLm558ai9KfIZvEkaIivJeFqdDuR2oVUV9nAQc6nqMHHnRiFmPJpUpXg== + POSTGRES_PASSWORD: AgASUNVrGocpv2dDca4E/cRK8IISrCS5z40ObiAzxsZql/+NdRfMzk1fUl5QF94KAgm3xe1V901SWNJvNVG/qKfHt83K8ARlWyHxFH8etc9diMQgXD4EuW21wVyWbI1lRaDSP56yqMMlOpgrgfLhN45BToiD0Dqe6s1lAdO7ozyBcNO9kEkHzM/bFmGgbDX/u8/c4bXXV3VSQueBPVDPMToEzIazCJPDQKn7PlmOzmANQ8xWa5avRpQfpsPcoatevDMnYDGyVEAlxBIAbaf8muyVD7yce6rFCYAQCUhqwyiu3YPVNBJT1PdmtNwZ2WHu42nuLcOkUoEWLtX/Ex/fnBhlvKvFBKCLBR2XPLjY/GkfoYXFN/+RbwCdvJnj77v11AxBLPrNpLx7sQmS0Gxz2ivzxexjuSVibyVO2pYqsi4idOOc8OtUEwbxN1WjncKMRz0WB5k2bOE4/DIjYIZ5KYz/+RVi6fR0CMKPuCgq76j3DHazMEKCicCdqw0iZaqyXKTIf3jzaAkdC0v6c/1by4d5z2NppDiGKwvLcwHYHlq2IKPPCjSdpu/efGI2CxijcHLtYpa5N2gg40T9oJRsf6NWu9lrvtdsoKiaWCo4hnkqmhGsNXiAqVerd7BslEkD8h5bnZYZPZ6IVZCZw86rwDOmICOj79lqtIcHDJyJra+n7wC3FlokBwR1bR3QcVJcqL2V2OBqlHQ2SCHv+uxL8d8IkHibnj23FJHlwYRBFNvw + POSTGRES_USER: AgA6zhC4W9S/+X+o2Gcj+0WfapqtUjZEmxpnMxZwQpYk5qaR1TrfFezKgKuGbnFWZ1HeUJM1gTlsOvg2wIlbKLHd8on8KeK4JhtR5GUd75icaW4/SSwWdODlHP6UlY1rxbc2uYxwLkbauRcjY1DKl633n/BXNYzAKFd1MbN0XsD3cMHgwDHy2MD6qW8VS80BEqj6ljNUPC/wJTR1CISH+rAiWmuuX+mf8whjqZxulqFFWaPuscC9ceHZT2bdhkC5uSBRXc5t6ZTiroRW50cT08J+u0W2Xm56ygJ6uuIxBWgTWKcAE1UNRIIXLOlK19j5yR6e216TaDTj06Spu6l7thfdPiRAx1kXMJhCK3Rhh5umWVrXxyomfA6IweUeI/kfLC3q3I5UGezZUMTou4QUsRVYwxnuyVsiDQJ0MWd0p5RVN690PbLxFebsg3YFW1R33UAvzhukO/Ct+QvWG+FfVUnaVrMh3nM+UnJau7w+9hdToG9wwvwBwCJRNRpCDo3ev1d+0AuBURXWnOSCUcifDsF6rKfCVxRMEVB5dLj5IYN3j0XRRMkvsE5UAHCFC5QFaUQD/5V6LSRvX1vV/aD88Ver57P0P4bYnNHLj02GP9OIV7HTv2jDJz3ZycfchXjRw9cIM7WmmF2HWlyCT608HD8NKf+dZlF7FJr+REYGCmNCvA4PIB6g6kwIOZzQ0KjVqFEJ0z+G7xXXjQ== + stalwart-db-password: AgBhrofehsfXWbdixnkOqdsNu8y9eDNIBW1g5ZgHcNAX0uq5Qa/84KguJZcNm4nzIuTs5KZTjTLvuync4cypKVKoqzthRwGWL01uAc3I8+gzjAsSLIzfWa1Dq9BZTEA0Q7oO3dBmdooAy+e5WR0MRhsF9VQWWwxM/UzQad4ihM/PBlkfvx+6Ql0pTp8m/K+E5YqXzNnla5eCra+zlzG3JWF7SOF82AnM6sLKzsHQzTv31z4I6pJTyoEtBNxbjsmmuCIh+fGPBusqTEY3vjEwVJJEaviq2B6rR9uz/sNF5bc6XtX0nAeyeQR4WURrTmGBDgwI85Z0/vDBsD9D+NTuoTitw4kTO8JcE2H4FUY8afurxpODvx6mmLaYnV8c7hLz4l33k9zCpjb8gHfNqQR/sKRu+iSeej12E0MgxUXnSL9nhLBltwA/6KpGHz4TxTG58/AopF9yDQIKOsZF7Lj4mJC3AQ7qlpwF8j/5wiMEr8OqyhAiOm6wmzIArZzb91SVlKitLmQEPBdOzx2kboZmeSJi/P52R6qFYm/bamGPTI5m6x+MnEXgur37eubnSlY/v65sfhdJb+Gm4B1QyS06CSG4VCgom1T5Fpc1zTWxgYN85OaHQVFR1T5jhOCgZPS01/rN3R1D/wryve8n2aGLZhf+ad8vKyXbcg25lIchzwWOXYk5nM0NOshw3Dk2/IcFfwja3QGak0ps7syZUHPY+wCPL9mrd7F4OqaLrZmbZHrv + template: + metadata: + creationTimestamp: null + name: stalwart-postgres-credentials + namespace: stalwart + type: Opaque diff --git a/apps/stalwart/stalwart-postgres.yaml b/apps/stalwart/stalwart-postgres.yaml new file mode 100644 index 0000000..a9eb435 --- /dev/null +++ b/apps/stalwart/stalwart-postgres.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: stalwart-postgres + namespace: stalwart + labels: + app: stalwart-postgres +spec: + type: ClusterIP + selector: + app: stalwart-postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 + protocol: TCP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: stalwart-postgres + namespace: stalwart + labels: + app: stalwart-postgres +spec: + serviceName: stalwart-postgres + replicas: 1 + selector: + matchLabels: + app: stalwart-postgres + template: + metadata: + labels: + app: stalwart-postgres + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: postgres + image: postgres:16-alpine + imagePullPolicy: IfNotPresent + ports: + - name: postgres + containerPort: 5432 + protocol: TCP + envFrom: + - secretRef: + name: stalwart-postgres-credentials + env: + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 500m + memory: 1Gi + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB" + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 6 + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + subPath: pgdata + volumeClaimTemplates: + - metadata: + name: postgres-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/stalwart/stalwart-s3-backup-sealed.yaml b/apps/stalwart/stalwart-s3-backup-sealed.yaml index 535aebc..4712aeb 100644 --- a/apps/stalwart/stalwart-s3-backup-sealed.yaml +++ b/apps/stalwart/stalwart-s3-backup-sealed.yaml @@ -14,6 +14,8 @@ spec: secret-key: AgAl5eIpafdecB7CDhUiVQ3ozrf+7ND9pTs6rKqlvbleVRFqsC+/wzGhmVh/1ZOliaDQDR0oUbPwESDuIF74GmYWLjJU//D8D/hnldkg6guJyE6mzkMsEy+LY6fKO6vlfeJocLscscheuKRl7wdXBqc17APvgX97YnH64GPNIypOx5Xhvp9br9C9BZEOkfQD0Dfq7mKxJqDDjUTecQRt6+EpLzdYhkZdWs6Mw9oQUFmto10AA6zoQ8b2p/CaLu8REmVxZzgfA6ve9CMeVq1t6olcNUnrj+yMtByPmsJL4bGZLKWAzdmGzeEOb7IZ8bAJ0tRlLLjzxq3oP4UQgNs9EgzJLe/2mv+3y6T01vDmTzjOWm398Gm9e7ZQThSnfJEDZe2UKfj6ejcEzLkKHVcFJ0haUpJYrCOQLgEfFzG264FuhCjZsIWxRA8IBpJ85/UoPcFAlplai44kEjY/pOtCembOHYd8HTJh1QpI1wyGzRe7r8li7WhaQSOFihU1ZLXEhIZ2NDF2p+qs6ywYlRvqF8gKGZRJvLcH9o3cEA7ju2WPDkX+dv9AtdONZhZniY0U5C0UfOQ6gapQ40VP+KrrrDy+FkzFCjJDshJY7XPkDHQOoUmnIRdbdDoXjH2gITsqQZhJalGEZqxlP65qSYTDkaPtUjIVbZLU7UibLF0niwhp6CQJX3AJSndH6Tk9pG2y8S9xE23tbtW64fyefu8RhoAp76pPjXxZ7kyQHRqmipJgcca0Hn4YkxrC template: metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous creationTimestamp: null name: stalwart-s3-backup namespace: stalwart diff --git a/apps/velero/Chart.yaml b/apps/velero/Chart.yaml new file mode 100644 index 0000000..073b2ca --- /dev/null +++ b/apps/velero/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: velero +description: Velero cluster-reconstruction backups for BasicStack (Kopia + Hetzner S3) +type: application +version: 1.0.0 +appVersion: "1.18.1" + +dependencies: + - name: velero + version: 12.1.0 + repository: https://vmware-tanzu.github.io/helm-charts diff --git a/apps/velero/values.yaml b/apps/velero/values.yaml new file mode 100644 index 0000000..af69093 --- /dev/null +++ b/apps/velero/values.yaml @@ -0,0 +1,105 @@ +velero: + # Pinned chart version 12.1.0 (Velero appVersion 1.18.1). + # AWS plugin follows Velero minor version: v1.14.x for Velero 1.18.x. + image: + repository: velero/velero + tag: v1.18.1 + pullPolicy: IfNotPresent + + # Bring the AWS object-storage plugin in as an initContainer that copies + # the binary into /plugins so the Velero server can load it. + initContainers: + - name: velero-plugin-for-aws + image: velero/velero-plugin-for-aws:v1.14.2 + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /target + name: plugins + + # Kopia file-system backup requires the node-agent daemonset on every + # node whose PVC data we back up. + deployNodeAgent: true + + # BackupStorageLocation credentials come from the SealedSecret + # apps/velero/velero-s3-credentials-sealed.yaml (key: cloud, INI format + # with aws_access_key_id / aws_secret_access_key for the dedicated + # `velero-prod` Hetzner OS credential from DEV-570). + credentials: + useSecret: true + existingSecret: velero-s3-credentials + + # Kopia backup-repository password (velero-repo-credentials Secret, + # key `repository-password`) is auto-discovered by Velero — this is a + # hard-coded constant in the Velero server (see pkg/repository/keys), + # so no extra wiring is needed. SealedSecret lives at + # apps/velero/velero-repo-credentials-sealed.yaml. + + # Turn OFF volume-snapshot CRDs — Hetzner Cloud Volumes have no CSI + # snapshot support, so filesystem backup via Kopia is the only path. + snapshotsEnabled: false + backupsEnabled: true + + configuration: + # Kopia uploader (unified repo). This is the Velero server default in + # 1.18 but we set it explicitly so any future default change does not + # silently rewrite our repos to restic. + uploaderType: kopia + + # No CSI feature flag — see snapshotsEnabled comment above. + features: "" + + backupStorageLocation: + - name: default + provider: aws + bucket: basicstack-velero + default: true + credential: + name: velero-s3-credentials + key: cloud + config: + # Hetzner Object Storage in Helsinki (hel1). Region is used by + # the AWS SDK signer; endpoint overrides the AWS endpoint URL. + region: hel1 + s3ForcePathStyle: "false" + s3Url: https://hel1.your-objectstorage.com + # publicUrl left unset — this backend is not user-facing. + + # No volumeSnapshotLocation entries — snapshotsEnabled: false above + # already suppresses the default snapshot location CRD. + + # Small resource footprint until we know real backup sizing (DEV-573). + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + + # Small memory request so node-agent fits on the tight 4 GiB CX22 workers + # (worker-1/2/3 sit around 95 % memory-request already). Actual peak usage + # during Kopia backup lands under 300 MiB per node in practice; limit is + # generous so a spike does not OOM-kill mid-backup. + nodeAgent: + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 1000m + memory: 1Gi + + metrics: + enabled: true + serviceMonitor: + enabled: false + + # Do not install the bundled `velero` CLI kubectl image — we run the + # CLI from cp-1. + kubectl: + image: + repository: bitnami/kubectl + resources: {} + + # No schedules here — those live in DEV-573. + schedules: {} diff --git a/apps/velero/velero-repo-credentials-sealed.yaml b/apps/velero/velero-repo-credentials-sealed.yaml new file mode 100644 index 0000000..3e8fad3 --- /dev/null +++ b/apps/velero/velero-repo-credentials-sealed.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: velero-repo-credentials + namespace: velero +spec: + encryptedData: + repository-password: AgCn0k/wQK76oVgVu9Eay7yMdxTKOE3RmJIojQcmPMerivAk72vw9F1jjmMDWW3f9X4HMc5MyQS9EXw9aes49047r1fZ4fA1yFgoBhuicS8ULpI4yI1ei9AVov6IQWx3FvarOlUIlQM40KC/es7mwlL/fSE2W5//u1zNp3Z+ujGdqAgBlvO0UQ/pGDNVKhWh6TBMNbzak4IDh6uNSNVAcqrzsh+2+RKPV4RRXnD1oJlTvtSri8EHQZdAAUaWxcIuyhnqeulStvViJKDC3Xw3PamrDXTYEPcAs8P90BVoDoJQwtxJ12Q+yqR1mjLvIZj81CKWXqNSUZDkAatRFjrfUYeiBAgmvnY5HL6f0eoJDVY+F7LfHslQHc4W/2z3YS19++oSKhffM4vwJxAWJqTbsKghvrA6hgFQM6btqGrhuHk3Q5NslKtlFnSgbRBst0FhBoyme0uWeBGk6szKKD8ClRuREJTWfof6pFGRM2O1IcKAhQqgJbamNHtU2yUCkLw732VBW3MJGQ+yLmlegjAmPKVLFDt4Yk5NrtTKrx9HZ3vohoPKwCz1CUizByVOXCsqEO1aogEszaQX9qCVsTCo6M8Zemw6OYCMMcbUcfPHRZmzd8o0j0Y+/6KTjP0fL5l9vR/ocJH+kXLnVsWZiFa3/TXQ0KEG1pPeCSVjQP7WHB1eLtrPH13ikvpyT8u6Ng+FPvOoTD36JrwSj8Een5HVc8oaoV50pXbbGqkepFIXZQ1te5nQL9XSP0AbRMcrqg== + template: + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + creationTimestamp: null + name: velero-repo-credentials + namespace: velero + type: Opaque diff --git a/apps/velero/velero-s3-credentials-sealed.yaml b/apps/velero/velero-s3-credentials-sealed.yaml new file mode 100644 index 0000000..c6f37bc --- /dev/null +++ b/apps/velero/velero-s3-credentials-sealed.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: velero-s3-credentials + namespace: velero +spec: + encryptedData: + cloud: AgAo2duq77obiTyFYdVyNANrMtx2iiOT6/cRHyYezhtr6YKlV2whmQBHXuRNhnjQyrmt3Gyl9+t/UZpJbJljqzesstfwRDZ9q1usXILVZE0ZsJB1GyQ5kwN/EQUeqhAYYUSZEUMXLAEkYTD2HE+sbWkNQrGXl1UJfPeEuDkRtCtQ+WNdZ3L53DLCjXQh3JV/4jIG+5bc56Sb33l/xtSSs5YBlUI/cgCM/Di0Xj5pyujxaqiU+vSCrpxiltkK7QDgcti1MbbL5YqeDox5/DwmJZvL1Of2PzODRpgTrDXbylWH8AaNRhzYcSJgv0ZBUwiqsLjFRdU9Oroevg2Uf5dO8FLUbAvx8ER97Afkt6fX7wTgcn3uj9/Ye3+BVOPRIj2l6VaJ3ul15Z+i+UCAAHe3jeujQhVHhZMRXGp5CnjkjJbiFIIL83cq6SfKq21pTEUkk5Ty5V844zDMymHHKvB52/LXS1i0LrELT/QzEKr0y+B3wp/JRFtvsliN+Bsweg4RroBsnVL4d0Aqp0zr3dHaUmTVDTycFGb/GFRTdsC+0JAJlplonJXcU/9h8mSRaBekusFTwMs25C6JHXPcXbI9qf8+qw6n/4vC86FkpyOWKNS2fs5r/Aut+8PC8Z2RcNUjnNUtt/i9FSkoQtIOwHRR0J8Zy+07TyysR0Zz2tB6C895SmHPI5isxQGZoAJHp+ypJwP/nR+RTdB648IfLLZyRINLEt58Nk4NM7B9tXOvk0JBXfB/knXy0wO54PvrnmLfxcE3mIy2/KKM5HK/b+QbSy97VqpXZekbcKJl18foUMsP7YitmP5pWfxBkg9m+3ZDemhCL48mLjr05KVrJ04r8UTMAlWwUw== + template: + metadata: + annotations: + argocd.argoproj.io/compare-options: IgnoreExtraneous + creationTimestamp: null + name: velero-s3-credentials + namespace: velero + type: Opaque diff --git a/apps/vikunja/README.md b/apps/vikunja/README.md new file mode 100644 index 0000000..c1f88c5 --- /dev/null +++ b/apps/vikunja/README.md @@ -0,0 +1,39 @@ +# Vikunja (vikunja.basicstack.de) + +Self-hosted task manager. Unified `vikunja/vikunja` container (API + frontend on port 3456). Backed by a namespace-local PostgreSQL. OIDC via Pocket ID (`auth.basicstack.de`); local login disabled. + +## Layout + +| File | Purpose | +|---|---| +| `vikunja-db-pvc.yaml` | 10 Gi encrypted PVC for Postgres data | +| `vikunja-files-pvc.yaml` | 10 Gi encrypted PVC for user-uploaded attachments (`/app/vikunja/files`) | +| `vikunja-postgres-deployment.yaml` | Postgres 16, pinned to `csi.hetzner.cloud/location=fsn1` | +| `vikunja-postgres-service.yaml` | ClusterIP `vikunja-postgres:5432` | +| `vikunja-deployment.yaml` | Vikunja app, pinned to `fsn1`, waits on Postgres | +| `vikunja-service.yaml` | ClusterIP `vikunja:80` → pod `3456` | +| `vikunja-ingress.yaml` | Traefik + `letsencrypt-prod` cert | +| `vikunja-db-credentials-sealed.yaml` | Postgres user password | +| `vikunja-oidc-secret-sealed.yaml` | Pocket ID client id + secret | +| `vikunja-app-secrets-sealed.yaml` | Vikunja JWT secret | + +## Node placement + +Both the app and Postgres pin to Hetzner location `fsn1` because their PVCs live on `hcloud-volumes-encrypted` (datacenter-local). + +## OIDC + +- Pocket ID client `Vikunja` (id `cdfff4ee-28ba-4aab-9f47-7e41b4b6b847`), callback `https://vikunja.basicstack.de/auth/openid/basicstack`. +- Pocket ID groups `vikunja_admins` and `vikunja_members`. +- Vikunja does not map OIDC groups to the admin flag. Promote the first user post-login: `kubectl -n vikunja exec deploy/vikunja -- /app/vikunja/vikunja user set-admin --admin `. +- **PKCE must be OFF on the Pocket ID client.** Vikunja 2.x's OAuth redirect (`frontend/src/helpers/redirectToProvider.ts`) does not send `code_challenge`, and the Go backend does not implement PKCE either. With `pkceEnabled: true` on Pocket ID the flow fails at `/authorize` with `invalid_request … 'code_challenge' parameter is missing`. Client is confidential (`isPublic: false`), so `client_secret` still authenticates the token exchange. + +## SMTP + +Reminder emails go through Stalwart on `stalwart-smtp.stalwart.svc.cluster.local:25` using unauthenticated in-cluster relay. Stalwart's port-25 listener trusts the pod-network CIDR (`10.244.0.0/16`), so no SMTP AUTH is required and no sealed secret is needed. Envelope-from is `vikunja@basicstack.de` (the mailbox already exists in Stalwart as principal `g`). + +If a future policy tightens the port-25 CIDR trust, switch to submission on `stalwart-smtp:587` with STARTTLS and a `vikunja-mailer` sealed secret (`VIKUNJA_MAILER_USERNAME`/`VIKUNJA_MAILER_PASSWORD` from a secretKeyRef, `VIKUNJA_MAILER_AUTHTYPE=plain`); creating that secret requires a working Stalwart admin session to (re)set the vikunja principal's password. + +## Change control + +Manifest changes flow through Argo CD (Application: `apps/app-vikunja.yaml`, auto-sync + prune + selfHeal). Sealed secrets are encrypted against the cluster-active sealed-secrets key. diff --git a/apps/vikunja/vikunja-app-secrets-sealed.yaml b/apps/vikunja/vikunja-app-secrets-sealed.yaml new file mode 100644 index 0000000..2fd4a2b --- /dev/null +++ b/apps/vikunja/vikunja-app-secrets-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: vikunja-app-secrets + namespace: vikunja +spec: + encryptedData: + jwt-secret: AgA2gSMP5tLy+2BeEe/vO3DHi7v4WO2HRZc2kTD/+BRII/CxCd3SIJYOh5O7OPNOq5Vp+F6oCi6GUg7ljyXuHU1IlHSSYWfjdGKS/mIvlKf2XRASOS1CefNSDKjU+x2FB3LPJG10v/0OG7bLKCj7QGq5y+QNc8dQrV7kqwAonG18iZzl5rcL4vN38B2V6kaO+s/tcz0uwmVOlUqblmv6WwrkMm5bVt9RQxcO5UjmDuf2KlnHBgvhRUhcBCbmrnI7J+FkVtq+2hiWwyz3hXAQAyy6ULMltYZvSy4ADseTV3yBdArUvY9e13kwfVZBjLe5EBzn0SKoQyY0XZQUYDmfQU+U5zFxsZLbYeekf7FUN8zHJ+mgvrT35PGrVazoz2rKI+9BrA7sstYJZ0ZUlj+nKnrbHYyaUhrlu3DYFvTHCOz8ylRxIsM5Ux819+RISpBhKkg6+mIhH2L26larI7UEY576c83TMhmCEOeX0es+MoomsAqeQi8vZ+OT5qz9dFZpzshXmwRYy1Ezm9d992iBF9EskB9IuyaozpA+GU9WwKAOj5GF3izj89nel9QPPQVBGHYvxXXfaXi3yQIpqD54nVPKC1oEhPNeCfVp3YcGSd/HOsl3tT/8G7ipD40jGVk5tgWxpymbvNZmcpm1CIb4SPYHOavJtqBcPY1F9k8a5yYl9sMqd64qzDmTlOQhRVWjD0mqCf2W/W3OvHo4dTu6Fy95jHrzoHsmmfNQQDVytCJK/3SmYUvh51nD8/CyiJxRAq3zPViFHKmRS0E5oCR0+zm3GlZTGwUmu1ROSIlsDgik/khmrZvYFLtF + template: + metadata: + creationTimestamp: null + name: vikunja-app-secrets + namespace: vikunja diff --git a/apps/vikunja/vikunja-db-credentials-sealed.yaml b/apps/vikunja/vikunja-db-credentials-sealed.yaml new file mode 100644 index 0000000..6237aa6 --- /dev/null +++ b/apps/vikunja/vikunja-db-credentials-sealed.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: vikunja-db-credentials + namespace: vikunja +spec: + encryptedData: + postgres-password: AgB+P7iBg+tPIDKMGmiPh9HREVb7mKCf1GvQfBLcF61QuWHt0/S1YBYCd4fF3PSA9ok916m6VeOoIQKB0p57XDSEeNewYNsXQgudS0k0JHNdRZER8JhbXpK53E7EY0xw4TD8Tc6WFqyzCFwsL1WpYvvDH3p25W8f3JJfB39D6Up9j5xT5Hufi0G5BJ+tbAujNJooa0SWZbtQPxMWG96fvjmTV5FKxzvwg1JOL4gq0JHBfzo9sKIbQdGpGJjX4JkPLCxA0jU6mcJKEFn80zkDbQBL3g5dXZaYmIAEogZkvytltR4ebI0zyDrSTPqXhJF5bFcRpM43VXz6ZcjgdtG5VPelvAuMqfJ1U7HdYhhJqTt0lvwUi03BjulIJJ52gUjlg2yQioOwfON/y3gRM1TgMHMFCyw32S7vCfR00MKVh7xrb4YceRUTbeHkTd+4TMcdpI11YHeah2scu82tzyD/cGPtpFv2LYyH7JrBcIXXhfIDccBRX4bb2se9VyhpmvEP5Q9SQjazo/BxdnJUYF6kzpJA9oOIjFMXcSDxZh/nwDkA90w4a9iyT9l0+7p3z/RjbXOMKybuQlXpfPrhtCRS6PM3Am5sgnIi/APRaujDa/2a3kCe7K0zZl4Wk3wlAc4mMjnoWs9w//HfMpQByM6GWeHWeoQ/Bj3xCVtACb6SNqTxIs4OP9hAVBnHqouH0O/zjqZNuBa5v7GDwmW6JU1cA+Y/UI4Z/ycu2X6AXBHp5CROheV9PdINTrgS + template: + metadata: + creationTimestamp: null + name: vikunja-db-credentials + namespace: vikunja diff --git a/apps/vikunja/vikunja-db-pvc.yaml b/apps/vikunja/vikunja-db-pvc.yaml new file mode 100644 index 0000000..01664a7 --- /dev/null +++ b/apps/vikunja/vikunja-db-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vikunja-db + namespace: vikunja + labels: + app: vikunja + component: database +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/vikunja/vikunja-deployment.yaml b/apps/vikunja/vikunja-deployment.yaml new file mode 100644 index 0000000..841e9ee --- /dev/null +++ b/apps/vikunja/vikunja-deployment.yaml @@ -0,0 +1,144 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vikunja + namespace: vikunja + labels: + app: vikunja + component: app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: vikunja + component: app + template: + metadata: + labels: + app: vikunja + component: app + spec: + hostname: vikunja + subdomain: basicstack + setHostnameAsFQDN: true + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + initContainers: + - name: wait-for-postgres + image: postgres:16-alpine + command: + - sh + - -c + - | + until pg_isready -h vikunja-postgres -U vikunja; do + echo "Waiting for PostgreSQL..." + sleep 2 + done + securityContext: + fsGroup: 1000 + containers: + - name: vikunja + image: vikunja/vikunja:2.6.0 + ports: + - name: http + containerPort: 3456 + env: + - name: VIKUNJA_SERVICE_PUBLICURL + value: https://vikunja.basicstack.de/ + - name: VIKUNJA_SERVICE_SECRET + valueFrom: + secretKeyRef: + name: vikunja-app-secrets + key: jwt-secret + - name: VIKUNJA_SERVICE_ENABLEREGISTRATION + value: "false" + - name: VIKUNJA_SERVICE_TIMEZONE + value: Europe/Berlin + - name: VIKUNJA_DATABASE_TYPE + value: postgres + - name: VIKUNJA_DATABASE_HOST + value: vikunja-postgres + - name: VIKUNJA_DATABASE_PORT + value: "5432" + - name: VIKUNJA_DATABASE_DATABASE + value: vikunja + - name: VIKUNJA_DATABASE_USER + value: vikunja + - name: VIKUNJA_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: vikunja-db-credentials + key: postgres-password + - name: VIKUNJA_DATABASE_SSLMODE + value: disable + - name: VIKUNJA_FILES_BASEPATH + value: /app/vikunja/files + - name: VIKUNJA_AUTH_LOCAL_ENABLED + value: "false" + - name: VIKUNJA_AUTH_OPENID_ENABLED + value: "true" + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_NAME + value: BasicStack SSO + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_AUTHURL + value: https://auth.basicstack.de + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_SCOPE + value: openid profile email vikunja_scope + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_CLIENTID + valueFrom: + secretKeyRef: + name: vikunja-oidc-secret + key: client-id + - name: VIKUNJA_AUTH_OPENID_PROVIDERS_BASICSTACK_CLIENTSECRET + valueFrom: + secretKeyRef: + name: vikunja-oidc-secret + key: client-secret + - name: VIKUNJA_MAILER_ENABLED + value: "true" + - name: VIKUNJA_MAILER_HOST + value: stalwart-smtp.stalwart.svc.cluster.local + - name: VIKUNJA_MAILER_PORT + value: "25" + - name: VIKUNJA_MAILER_FORCESSL + value: "false" + - name: VIKUNJA_MAILER_SKIPTLSVERIFY + value: "true" + - name: VIKUNJA_MAILER_FROMEMAIL + value: vikunja@basicstack.de + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: "1" + memory: 512Mi + livenessProbe: + httpGet: + path: /api/v1/info + port: 3456 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/v1/info + port: 3456 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + volumeMounts: + - name: files + mountPath: /app/vikunja/files + volumes: + - name: files + persistentVolumeClaim: + claimName: vikunja-files diff --git a/apps/vikunja/vikunja-files-pvc.yaml b/apps/vikunja/vikunja-files-pvc.yaml new file mode 100644 index 0000000..1f830cf --- /dev/null +++ b/apps/vikunja/vikunja-files-pvc.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: vikunja-files + namespace: vikunja + labels: + app: vikunja + component: files +spec: + accessModes: + - ReadWriteOnce + storageClassName: hcloud-volumes-encrypted + resources: + requests: + storage: 10Gi diff --git a/apps/vikunja/vikunja-ingress.yaml b/apps/vikunja/vikunja-ingress.yaml new file mode 100644 index 0000000..d3db329 --- /dev/null +++ b/apps/vikunja/vikunja-ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: vikunja + namespace: vikunja + labels: + app: vikunja + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + tls: + - hosts: + - vikunja.basicstack.de + secretName: vikunja-tls + rules: + - host: vikunja.basicstack.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: vikunja + port: + number: 80 diff --git a/apps/vikunja/vikunja-oidc-secret-sealed.yaml b/apps/vikunja/vikunja-oidc-secret-sealed.yaml new file mode 100644 index 0000000..b4350d7 --- /dev/null +++ b/apps/vikunja/vikunja-oidc-secret-sealed.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: vikunja-oidc-secret + namespace: vikunja +spec: + encryptedData: + client-id: AgBdzdW0SxhmOyhOpC/jkBJ4UtD0qrRrytVp7fInK3bxiiVBHsIU8r3Hoe2iYZU75IyITogmcGImKH9pivxPMhj5jBqFeCExQadt/pqXHL9TmxmNpIx7FmnlrxaZ+zeqeKRMDhYJnxSeNiuRDVZEEnsBhmUc0gvz8Pq/x82ExY++MQjQQu2xB6XQ7DTyzVDx32yZAvBU6aRFhTtMTqPAvjsMHp6pKmciauS7LmffCVIJvnJUaUKIELHPDyWsMcQvWpPQrPKGkvmGruhXbct6LxPFKdkbsQMUWvj7emSemmbJk0VmGPtVxI7cTmHIJ6l4kr8aJfJ9fv2gMzbgprYVZHJ17qhPvvUv5iTUcYu5FiMiy3QtNHYMP+VyhWvj9aUH8ypFYwf0S3/fRobEtyBxnBLCmnIt4lvS0ABQ49YwdtWnBUXT1jP4Xzk3gWM8fe1LsOuUZXMgFoGMXtxuodhZFhTELjdKPaYTY/DDU6iRMpk/nUsQy1IGls1CySEB+taiQDRNMZmQAFHdoFOxX8sZyPKdYL3wgKDweqcLoI0UKjapkHdpcoWccqg0ky/f5VWWlD7kHWLXYeosH6ucLAKR/mRHoyOfIy1dEpYYevfv5UM4Lewnr8epLse/x3ymsBNaSIhxt8PxnFYe/Gi1rO8MAgTPNcw2pr2z5jiW8CXUPLdXezleoctl4VGg97pkYVibn0BJ+VrZOTgO/p/df5D0v7h7DSsvdbpA7XxkaTnL62JRhC3RDXU= + client-secret: AgA3ZQ5Da1D2tcNBA9RVf7YYNLBvBjKf+OArLLQSKKUDGAULoC0BQUn+T7rctuOANFeKLOYE2OjJQhURoi69xFyQ3d868s4a2Ipo3QwUV2vtoVJnz7l2fGXCzAOTcJhKonHdJooklfeK+gwi80sRXV1IxxDu778yKSWnFUd61vUPbhKgVgtXDM4IItNtctGc4Drz6dvrOHkyM1m4kxGbXUclpN+HztufQrO7W+ba23bZRCejaYz3sQ6IHmM50HXzRPD5Rdb2ALQFm9V7zdSuUQ8njNrXHfgqMSiDWoCzdpTKG50mBPqmait/yyNVnOAd8J906RZk27LnAY9zV1WiMUSImNS6IzuLV0DZTn6I4cWgnemIwNAdNR4/x7TwWNdVS3A3jH8q6S1IP5g65P40SmP27PfFx+L7jx72Om8HXBY8TpMtWwGFcjyEGy61tKuimXd65+o0UkXEmQfJKfnugnSM3yCufEat2PIE2+Arb7+pRh6VtzaBowLdo32OcqS5GMmFLhisv5cR016IIIbSnaLkIGpiZEHSvpcLoGOJm8H+1P6MPsHWfjWtKZy6y8Ri/B9zNHlUx506Kij5YOR+tH3B3ip4PIa2LRlRCAbzBb/WCwzP72WzK8PU/sSujTa+JgxBoNGt3fg2iqBmoaU3UrhVoTTcXhEn7pnvIlk60jZKTscC+thS++VHMnf7LjLAA2ksu9G6qKpWy5DAERYi/MC6YdYz0eoIBSa/NEHLi94zFA== + template: + metadata: + creationTimestamp: null + name: vikunja-oidc-secret + namespace: vikunja diff --git a/apps/vikunja/vikunja-postgres-deployment.yaml b/apps/vikunja/vikunja-postgres-deployment.yaml new file mode 100644 index 0000000..5c80921 --- /dev/null +++ b/apps/vikunja/vikunja-postgres-deployment.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vikunja-postgres + namespace: vikunja + labels: + app: vikunja + component: database +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: vikunja + component: database + template: + metadata: + labels: + app: vikunja + component: database + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: csi.hetzner.cloud/location + operator: In + values: + - fsn1 + containers: + - name: postgres + image: postgres:16-alpine + ports: + - name: postgres + containerPort: 5432 + env: + - name: POSTGRES_DB + value: vikunja + - name: POSTGRES_USER + value: vikunja + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: vikunja-db-credentials + key: postgres-password + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + livenessProbe: + exec: + command: + - pg_isready + - -U + - vikunja + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + exec: + command: + - pg_isready + - -U + - vikunja + initialDelaySeconds: 5 + periodSeconds: 5 + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + volumes: + - name: data + persistentVolumeClaim: + claimName: vikunja-db diff --git a/apps/vikunja/vikunja-postgres-service.yaml b/apps/vikunja/vikunja-postgres-service.yaml new file mode 100644 index 0000000..b1bc07c --- /dev/null +++ b/apps/vikunja/vikunja-postgres-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: vikunja-postgres + namespace: vikunja + labels: + app: vikunja + component: database +spec: + type: ClusterIP + selector: + app: vikunja + component: database + ports: + - name: postgres + port: 5432 + targetPort: 5432 diff --git a/apps/vikunja/vikunja-service.yaml b/apps/vikunja/vikunja-service.yaml new file mode 100644 index 0000000..dee7bae --- /dev/null +++ b/apps/vikunja/vikunja-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: vikunja + namespace: vikunja + labels: + app: vikunja + component: app +spec: + type: ClusterIP + selector: + app: vikunja + component: app + ports: + - name: http + port: 80 + targetPort: 3456 diff --git a/docs/monitoring/restic-restore.md b/docs/monitoring/restic-restore.md new file mode 100644 index 0000000..3aadfca --- /dev/null +++ b/docs/monitoring/restic-restore.md @@ -0,0 +1,464 @@ +# Restic Restore Runbook — Hetzner Object Storage + +Author: CTO agent (`4b5f09a2-22d8-4e3d-9ac4-46d008ad1385`) +Ticket: [DEV-488](/DEV/issues/DEV-488) +Parent: [DEV-482](/DEV/issues/DEV-482) (Option 4 — restic → Hetzner OS) + +The monitoring stack's data-durability layer is a client-side-encrypted +restic repo set stored in a single Hetzner Object Storage bucket. This +document is the operator playbook for **restoring** those snapshots +into a scratch namespace when we need to (a) recover from a real loss +event, or (b) run the quarterly restore drill that keeps the +[`backup-volumes` retirement plan](/DEV/issues/DEV-489) honest. + +## Repo layout + +Single bucket, four repo prefixes: + +| repo prefix | source | writer CronJob | tag | +| ---------------------------------------------- | --------------------------------------------- | ---------------------- | --------------- | +| `restic/loki` | PVC `loki-storage-encrypted` | `backup-loki-restic` | `loki` | +| `restic/grafana` | PVC `grafana-storage` (worker-2) | `backup-grafana-restic`| `grafana` | +| `restic/k8s-resources` | `kubectl get -o yaml` (stdin) | `backup-k8s-resources` | `k8s-resources` | +| `restic/prometheus` | PVC `prometheus-data-encrypted` (excl. WAL) | `prometheus-backup` | `prometheus` | + +Password + endpoint + bucket + AWS creds live in `SealedSecret` +`monitoring-s3-backup` (namespace `monitoring`). Keys: + +- `restic-password` — 32-byte random, generated in DEV-484, sealed + locally, mirrored to Passbolt entry `restic / monitoring backups`. +- `access-key`, `secret-key` — Hetzner S3 credentials. +- `endpoint` — e.g. `https://hel1.your-objectstorage.com`. +- `bucket` — Hetzner bucket name (all four restic repos share it). + +Encryption is done **client-side by restic**. Hetzner Object Storage +has no SSE-S3 / SSE-KMS ([FAQ](https://docs.hetzner.com/storage/object-storage/faq/general/)); +the CTO's [Hetzner Object Storage encryption memory](../../memory/hetzner-object-storage-encryption.md) +covers the constraints. + +## Credentials projection + +Any restore pod must project **only** `monitoring-s3-backup` — never +mount cluster admin creds into the drill namespace. The pattern below +copies the secret cross-namespace once and lets restic project it +read-only. + +```bash +kubectl create namespace restore-drill +kubectl -n monitoring get secret monitoring-s3-backup -o json | + jq 'del(.metadata.namespace,.metadata.resourceVersion,.metadata.uid, + .metadata.creationTimestamp,.metadata.ownerReferences, + .metadata.annotations,.status) | + .metadata.namespace="restore-drill"' | + kubectl apply -n restore-drill -f - +``` + +## Restore pod + +Throwaway pod pinned to the API-server node (no data mount, just the +S3 secret). The `emptyDir` holds the restored tree; deleting the pod +wipes it. + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: restore-drill + namespace: restore-drill + labels: { app: restore-drill } +spec: + restartPolicy: Never + containers: + - name: restic + image: harbor.basicstack.de/library/restic:0.19.1 # matches CronJob image (DEV-493, bumped in DEV-541) + command: ["sleep", "3600"] + env: + - { name: AWS_ACCESS_KEY_ID, valueFrom: { secretKeyRef: { name: monitoring-s3-backup, key: access-key } } } + - { name: AWS_SECRET_ACCESS_KEY, valueFrom: { secretKeyRef: { name: monitoring-s3-backup, key: secret-key } } } + - { name: RESTIC_PASSWORD, valueFrom: { secretKeyRef: { name: monitoring-s3-backup, key: restic-password } } } + - { name: S3_ENDPOINT, valueFrom: { secretKeyRef: { name: monitoring-s3-backup, key: endpoint } } } + - { name: S3_BUCKET, valueFrom: { secretKeyRef: { name: monitoring-s3-backup, key: bucket } } } + volumeMounts: + - { name: work, mountPath: /work } + volumes: + - name: work + emptyDir: {} +``` + +Apply it, then `kubectl -n restore-drill exec restore-drill -- ` +for everything below. + +## Per-repo restore commands + +`RESTIC_REPOSITORY` is set inline per repo to keep repo scope explicit. +All commands run inside the scratch pod. + +### Loki + +```sh +export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/loki" +restic snapshots +restic restore latest --target /work/loki --tag loki +find /work/loki/source -type f +``` + +Restored tree lands under `/work/loki/source/…` because the backup +source was `/source` inside the CronJob container. + +### K8s resources + +```sh +export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/k8s-resources" +restic snapshots +restic restore latest --target /work/k8s --tag k8s-resources +ls -lh /work/k8s/cluster.yaml +``` + +`cluster.yaml` is the concatenated dump written by the +`backup-k8s-resources` CronJob's `kubectl-dump` init container. + +### Grafana + +```sh +export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/grafana" +restic snapshots +restic restore latest --target /work/grafana --tag grafana +``` + +Follows the loki pattern (restored tree under `/work/grafana/source/`). + +### Prometheus + +```sh +export RESTIC_REPOSITORY="s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/prometheus" +restic snapshots +restic restore latest --target /work/prometheus --tag prometheus +find /work/prometheus/source -maxdepth 1 -type d | head +``` + +Restored tree lands under `/work/prometheus/source/…`. **WAL and +`chunks_head/` are excluded on purpose** — see the "compaction race" +notes at the top of `apps/monitoring/prometheus-backup-cronjob.yaml`. +Expect the last ~15 s of ingested samples to be lost on recovery; the +compacted 2h/24h blocks are complete. + +**Integrity check — `promtool tsdb analyze` (required for the DEV-492 +drill).** The snapshot must load cleanly through Prometheus's own +verifier before we call the drill green. + +```sh +kubectl -n restore-drill run promtool-check --rm -it --restart=Never \ + --image=prom/prometheus:v2.53.1 \ + --overrides='{"spec":{"containers":[{"name":"promtool-check","image":"prom/prometheus:v2.53.1","command":["sh","-c","for b in /work/prometheus/source/*/; do echo \"--- $b\"; promtool tsdb analyze /work/prometheus/source \"$(basename $b)\" || exit 1; done"],"volumeMounts":[{"name":"work","mountPath":"/work"}]}],"volumes":[{"name":"work","emptyDir":{}}]}}' +``` + +Simpler drill path (the restore pod already has restic; `promtool` is +not shipped in `restic/restic`, so run it from a `prom/prometheus` +sidecar or an interactive pod that mounts the same `emptyDir` — see +DEV-492 drill entry for the concrete two-pod recipe used on the +first run). + +Exit 0 on every block = pass. A non-zero exit on any block means the +compaction-race mitigation slipped and the snapshot is corrupt. +Re-run the CronJob and re-drill. + +## Integrity checks + +Two layers. + +**Repository self-check** — cryptographic integrity of packs and +snapshots, no source needed: + +```sh +restic check # metadata only +restic check --read-data-subset=5% # sample-read + decrypt 5% of packs +``` + +The CronJobs run `--read-data-subset=5%` on every write. A restore +drill should re-run at minimum `restic check` end-to-end. + +**Source parity** — sha256 the restored tree against a fresh readout +of the live PVC. For loki, exec into the live pod: + +```sh +LOKI=$(kubectl -n monitoring get pods -l app=loki -o jsonpath='{.items[0].metadata.name}') +kubectl -n monitoring exec "$LOKI" -- sh -c ' + cd /loki && find . -type f ! -path "./lost+found/*" -exec sha256sum {} +' \ + > /tmp/live.sha256 +kubectl -n restore-drill exec restore-drill -- sh -c ' + cd /work/loki/source && find . -type f -exec sha256sum {} +' \ + > /tmp/restored.sha256 +diff <(sort /tmp/live.sha256) <(sort /tmp/restored.sha256) +``` + +Empty diff = parity. Any drift is expected only on files that Loki is +actively writing (WAL segments) — capture the snapshot when Loki is +quiet, or accept a WAL delta and re-hash the boltdb-shipper / +chunks trees only. + +For k8s-resources, parity is meaningless (a live dump varies by the +second); rely on `restic check` and on being able to `grep` the +restored `cluster.yaml` for expected namespaces / secrets. + +## Cleanup + +```sh +kubectl -n restore-drill delete pod restore-drill --wait +kubectl delete namespace restore-drill +``` + +Deleting the namespace tears down the projected secret. The `emptyDir` +lives in the pod's ephemeral scratch on the node; the sleep container +never persists anything outside `/work`. + +## Rotation and disaster recovery + +If the `restic-password` is lost: + +1. Recover the plaintext from Passbolt (`restic / monitoring backups`). +2. If Passbolt is also lost, the data is unrecoverable by design — + client-side encryption with a lost key cannot be reversed. + +To rotate: + +```sh +# on any workstation with kubectl + kubeseal + the restic password +NEW=$(openssl rand -base64 32) +restic key add # opens editor for new password; + # paste NEW and confirm +restic key list +restic key remove +# then re-seal the SealedSecret with NEW, redeploy, Argo syncs. +``` + +Rotate annually or immediately on suspected compromise. + +## Restic image (Harbor mirror) + +All three restic CronJobs (`backup-loki-restic`, `backup-grafana-restic`, +`backup-k8s-resources`) reference a Harbor-hosted copy of upstream to +keep the backup pipeline off the Docker Hub pull path and immune to +upstream retagging: + +- Manifest reference: `harbor.basicstack.de/library/restic:0.19.1` +- Upstream: `docker.io/restic/restic:0.19.1` +- Ticket: [DEV-493](/DEV/issues/DEV-493) (bumped to 0.19.1 in [DEV-541](/DEV/issues/DEV-541)) + +The `library` project is public, so no `imagePullSecret` is required +on the CronJob pods. + +### Tag-bump procedure + +Run this when we want to move restic to a new pinned tag (e.g. 0.17.3 +→ 0.17.4). Do the mirror push **before** editing manifests so Argo +cannot roll pods onto an unmirrored tag. + +1. **Pick and verify the upstream tag.** Confirm the tag exists on + Docker Hub and (ideally) read the upstream restic release notes for + breaking changes: + + ```sh + curl -s "https://hub.docker.com/v2/repositories/restic/restic/tags/0.17.4" + ``` + +2. **Mirror the image to Harbor** with a one-shot in-cluster crane + Job. Requires an admin (or `library`-scoped robot) Harbor + credential; the Job auth secret is throwaway. + + ```sh + # from the CTO workstation with kubectl + Harbor admin access + NEW_TAG=0.17.4 + ADMIN_PW=$(kubectl -n harbor get secret harbor-secrets \ + -o jsonpath='{.data.harborAdminPassword}' | base64 -d) + AUTH=$(printf 'admin:%s' "$ADMIN_PW" | base64 -w0) + cat >/tmp/dc.json <` under + the `restic` container. Bump `` in all three, commit, push. + +6. **Let Argo sync** — the `monitoring` Application picks up the new + manifests. Watch for successful reconciliation: + + ```sh + kubectl -n argocd get application monitoring \ + -o jsonpath='{.status.sync.status}{" "}{.status.health.status}{"\n"}' + ``` + +7. **Trigger one CronJob run to prove the Harbor pull is green.** + Create a manual Job from any of the CronJobs (loki is fine) and + inspect its logs: + + ```sh + kubectl -n monitoring create job --from=cronjob/backup-loki-restic \ + smoke-restic-$(date +%Y%m%d-%H%M%S) + ``` + + The Pod should pull from `harbor.basicstack.de/library/restic:` + (`kubectl describe pod ...` → Events → `Pulling image …`) and + finish with `restic backup /source` output ending in a snapshot id. + +8. **Update this doc's "restore pod" template** if the new tag is + incompatible with the existing restore pod command — the drill pod + image must match the CronJob image, otherwise the restore-drill + won't round-trip. + +**Rollback:** if the new tag misbehaves, edit the three manifests back +to the last-known-good tag, commit, and let Argo re-sync. The old +tag's manifest remains in Harbor until it's explicitly deleted, so +rollback is a manifest change only. + +## Drill log + +Every drill appends to this section. Include: + +- date (UTC), operator, snapshot IDs restored, parity result, + restic check result, cleanup confirmation. + +### 2026-08-16 — First drill (DEV-488) + +- Operator: CTO agent, run [DEV-488](/DEV/issues/DEV-488). +- Cluster: k3s at `178.105.17.239` (Hetzner). +- Snapshots: + - `restic/loki`: `97092888` (2026-08-16 15:44:21Z), parent `98c6fee6` + (2026-08-16 15:26:10Z). Both tag=`loki`, host=`k3s`, paths=`/source`. + - `restic/k8s-resources`: `9b0155cf` (2026-08-16 15:40:28Z), + tag=`k8s-resources`, paths=`/cluster.yaml`. +- Restore output: + - loki: `Restored 14 files/dirs (292 B) in 0:00` — matches live PVC + which is nearly empty (fresh Loki, no ingested chunks yet). + - k8s-resources: `Restored 1 files/dirs (11.167 MiB) in 0:00` — + `cluster.yaml` = 11 709 295 bytes, sha256 + `5aa93f7326b5cbd2408cb763395bba259de6cc0f99b0c8e1bca4421edc7931c0`. +- Parity — loki restored tree vs. live loki-storage-encrypted PVC: + + | file | restored sha256 | live sha256 | match | + | ---------------------------------------- | ------------------------------------------------------------------ | ----------- | ----- | + | `boltdb-shipper-active/uploader/name` | `51215284bd61bf79d48ccd1aec59445bfa28dec7b2cdce4d9476bc0b61342813` | identical | yes | + | `chunks/loki_cluster_seed.json` | `8d214a355d0057586e1c142d5f5d2658ccec39bd66e193b37dc12c21d6d7edd5` | identical | yes | + | `wal/checkpoint.019215/00000000` | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | identical | yes | + | `wal/00019216` | `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` | identical | yes | + +- k8s-resources sanity: `cluster.yaml` opens with a valid v1 + Namespace list including `argocd`, `basicstack-web`, `monitoring` + etc. No parse errors. +- `restic check --read-data-subset=5%` was run by the writing + CronJob on both repos; drill relied on that. Re-running end-to-end + `restic check` is a follow-up for the quarterly drill. +- Cleanup: pod + `restore-drill` namespace deleted; scratch tree + discarded with the emptyDir. + +**Outcome: PASS** for `restic/loki` and `restic/k8s-resources`. + +Next drill target: 2026-11-16 (quarterly). Include `restic/grafana` +once its first CronJob run has produced ≥ 1 snapshot. + +### 2026-08-16 — Prometheus migration drill (DEV-492) + +Migrated `prometheus-backup` from `rclone sync` (plaintext at rest) to +restic (client-side encrypted). Same-day full-cycle drill against the +freshly-populated `restic/prometheus` repo. + +- Operator: CTO agent, run [DEV-492](/DEV/issues/DEV-492). +- Repo: `s3:${S3_ENDPOINT}/${S3_BUCKET}/restic/prometheus`, initialised + by first CronJob run (repo id `f0eda506fd`). +- Backup runs (manual, from the deployed CronJob): + - `71420465` (2026-08-16 16:16:35Z) — first run, cold repo, + `restic backup` walk 5:10, 85 files 42 dirs, 8.017 GiB added + (2.441 GiB stored after compression + dedup), exit 0. `restic + check --read-data-subset=5%`: no errors. + - `825f983a` (2026-08-16 16:23:56Z) — second run, warm repo, 17 s + total (no new blocks — dedup 100 %), `restic check` clean. +- Drill: `harbor.basicstack.de/library/restic:0.17.3` (restore) + + `prom/prometheus:v2.53.1` (promtool) sidecars sharing an `emptyDir` + in namespace `restore-drill-dev492`. + - `restic restore latest --tag prometheus --target /work`: + `Restored 127 files/dirs (8.069 GiB) in 0:34`. + - Excluded paths verified absent on restored tree: `wal/`, + `chunks_head/`, `lost+found/` restored as empty dirs; `lock` and + `queries.active` not present at all. + - `promtool tsdb list /work/source` — 19 blocks parsed, all with + valid ULID + duration + samples/chunks/series counts. + - `promtool tsdb analyze /work/source ` — ran against every + block dir; 0 failed. Summary lines validated non-zero `Total + Series` and reasonable `Duration` (2h / 18h / 54h buckets, matching + Prometheus's compaction schedule). + - End-to-end `restic check`: `no errors were found` (metadata pass; + on the freshly-initialised repo `--read-data-subset=5%` was + already exercised by the writing CronJob). +- Cleanup: `restore-drill-prom` pod + `restore-drill-dev492` namespace + deleted; scratch tree discarded with the `emptyDir`. Manual + `prom-restic-manual-{01,02}` jobs deleted from `monitoring`. + +**Outcome: PASS** for `restic/prometheus`. All Definition-of-Done +items on [DEV-492](/DEV/issues/DEV-492) satisfied — backup encrypted +at rest, `promtool tsdb analyze` clean, docs updated, restic password +handling documented (shared `monitoring-s3-backup` SealedSecret, +rotation procedure covers all four repos). + +Next drill target for `restic/prometheus`: 2026-11-16 alongside the +other three repos. diff --git a/infrastructure/ADD_WORKER_NODE.md b/infrastructure/ADD_WORKER_NODE.md new file mode 100644 index 0000000..1b94239 --- /dev/null +++ b/infrastructure/ADD_WORKER_NODE.md @@ -0,0 +1,345 @@ +# Adding a Worker Node to k3s Cluster - Quick Instruction + +**Purpose:** Step-by-step checklist for adding a new worker node to the BasicStack k3s cluster. + +**Audience:** CTO agent, infrastructure operators + +**Prerequisites:** +- Hetzner Cloud access (console or hcloud CLI with credentials) +- kubectl access to the cluster +- SSH access to control plane (178.105.17.239) + +--- + +## Step 1: Create Hetzner Cloud VM + +### Via Hetzner Cloud Console + +1. Navigate to [Hetzner Cloud Console](https://console.hetzner.cloud/) +2. Select the BasicStack project +3. Click "Add Server" +4. Configure: + - **Name:** `k3s-worker-N` (use next available number) + - **Location:** Match existing cluster (check with `kubectl get nodes -o wide`) + - **Image:** Ubuntu 24.04 LTS + - **Type:** CX32 or larger (minimum 4 vCPU, 8 GB RAM) + - **Network:** Attach to existing private network (`10.42.1.0/24`) + - **SSH Keys:** Select both `cto-paperclip` AND `silentmaxx` + - **Firewall:** Attach `fw-k3s` +5. Click "Create & Buy now" +6. **Record the IPs:** + - Private IP: `________________` (from Hetzner network tab) + - Public IP: `________________` (from server overview) + +### Via hcloud CLI + +```bash +# List available networks +hcloud network list + +# List available SSH keys (verify both keys exist) +hcloud ssh-key list | grep -E 'cto-paperclip|silentmaxx' + +# Create the server (adjust location as needed) +hcloud server create \ + --name k3s-worker-4 \ + --type cx32 \ + --image ubuntu-24.04 \ + --ssh-key cto-paperclip \ + --ssh-key silentmaxx \ + --network k3s-network \ + --firewall fw-k3s \ + --location nbg1 + +# Record the IPs from output +``` + +--- + +## Step 2: Verify Firewall Configuration + +**Why:** Flannel VXLAN uses public IPs for pod networking tunnels. Without the firewall rule, pods cannot communicate. + +```bash +# Check if public IP is in fw-k3s +hcloud firewall describe fw-k3s | grep + +# If missing, add the rule: +hcloud firewall add-rule fw-k3s \ + --direction in \ + --protocol tcp \ + --source-ips /32 \ + --port any +``` + +**Checklist:** +- [ ] Firewall rule exists for node's public IP + +--- + +## Step 3: Verify SSH Access + +Test connectivity before proceeding: + +```bash +# Test public IP +ssh root@ 'hostname && date' + +# Test private IP (if accessible) +ssh root@ 'hostname && date' +``` + +**Checklist:** +- [ ] SSH access confirmed to public IP +- [ ] SSH access confirmed to private IP + +--- + +## Step 4: Run Provisioning Script + +**Location:** `infrastructure/scripts/provision-k3s-worker.sh` + +**What it does:** +- Detects current k3s cluster version +- Prompts for firewall confirmation +- Retrieves join token from control plane +- Installs k3s with version pinning +- Verifies successful cluster join + +**Execution:** + +```bash +cd /path/to/stack.basicstack.de/infrastructure/scripts + +# Syntax: ./provision-k3s-worker.sh +./provision-k3s-worker.sh + +# Example: +# ./provision-k3s-worker.sh 10.42.1.6 167.233.121.122 +``` + +**During execution:** +- Confirm firewall update when prompted (press Enter after verifying Step 2) +- Wait for k3s installation (may take 5-10 minutes) +- Review the final node list output + +**Checklist:** +- [ ] Script executed successfully +- [ ] No error messages in output +- [ ] Node appears in final `kubectl get nodes` output + +--- + +## Step 5: Verify Node Health + +Run these verification commands: + +```bash +# 1. Check node status (should be Ready) +kubectl get nodes -o wide + +# 2. Verify internal IP matches Hetzner private IP +kubectl get node k3s-worker-N -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}' +# Expected: 10.42.1.X (the private IP from Step 1) + +# 3. Check k3s version consistency +kubectl get nodes -o custom-columns=NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion +# All nodes should have the same version + +# 4. Verify flannel configuration +kubectl get node k3s-worker-N -o yaml | grep flannel +# Should see flannel.alpha.coreos.com annotations + +# 5. Check pod CIDR assignment +kubectl get node k3s-worker-N -o jsonpath='{.spec.podCIDR}' +# Should return something like 10.244.X.0/24 +``` + +**Expected Results:** +- Node status: `Ready` +- Internal IP: Matches private IP from Hetzner network +- k3s version: Matches existing nodes +- Flannel annotations: Present +- Pod CIDR: Assigned (10.244.X.0/24) + +**Checklist:** +- [ ] Node status is Ready +- [ ] Internal IP is correct (Hetzner private IP) +- [ ] k3s version matches cluster +- [ ] Flannel annotations present +- [ ] Pod CIDR assigned + +--- + +## Step 6: Label the Node (Optional) + +Add standard worker label: + +```bash +kubectl label nodes k3s-worker-N node-role.kubernetes.io/worker=worker +``` + +**Checklist:** +- [ ] Node labeled + +--- + +## Step 7: Monitor Pod Scheduling + +Watch for pods to be scheduled on the new node: + +```bash +# Check overall pod distribution +kubectl get pods --all-namespaces -o wide | grep k3s-worker-N + +# If adding capacity for specific workload (e.g., Stalwart): +kubectl get pod -n stalwart -l app=stalwart -o wide + +# Watch for scheduling events +kubectl get events --sort-by='.lastTimestamp' | grep k3s-worker-N +``` + +**Checklist:** +- [ ] Pods are being scheduled on new node +- [ ] Target workload (if specified) successfully scheduled + +--- + +## Step 8: Update Documentation + +Update the cluster architecture table in `infrastructure/K3S_OPERATIONS.md`: + +1. Get the assigned pod CIDR: + ```bash + kubectl get node k3s-worker-N -o jsonpath='{.spec.podCIDR}' + ``` + +2. Edit `infrastructure/K3S_OPERATIONS.md` and add to the "Node CIDRs" table: + ```markdown + | k3s-worker-N | 10.244.X.0/24 | 10.42.1.Y | | + ``` + +3. Commit the change: + ```bash + cd infrastructure + git add K3S_OPERATIONS.md + git commit -m "docs: add k3s-worker-N to cluster architecture table + + Added new worker node with Pod CIDR 10.244.X.0/24. + Node added to resolve resource constraints for workload scheduling. + + Co-Authored-By: Paperclip " + git push + ``` + +**Checklist:** +- [ ] K3S_OPERATIONS.md updated with new node +- [ ] Changes committed and pushed + +--- + +## Success Criteria + +All checkboxes above should be completed: + +- [ ] VM created with correct specifications +- [ ] Both SSH keys (cto-paperclip, silentmaxx) added +- [ ] Firewall rule configured +- [ ] SSH access verified +- [ ] Provisioning script executed successfully +- [ ] Node status: Ready +- [ ] Internal IP matches Hetzner private IP +- [ ] k3s version matches cluster +- [ ] Flannel annotations present +- [ ] Pod CIDR assigned +- [ ] Pods scheduling on new node +- [ ] Documentation updated + +--- + +## Rollback Procedure + +If issues occur and the node needs to be removed: + +```bash +# 1. Drain the node (move all pods off) +kubectl drain k3s-worker-N --ignore-daemonsets --delete-emptydir-data --timeout=5m + +# 2. Delete from cluster +kubectl delete node k3s-worker-N + +# 3. Remove firewall rule (get rule ID first) +hcloud firewall describe fw-k3s +hcloud firewall delete-rule fw-k3s + +# 4. Delete Hetzner VM +hcloud server delete k3s-worker-N +``` + +--- + +## Troubleshooting + +### Node Not Appearing in kubectl get nodes + +**Check:** +1. Firewall rule is configured (Step 2) +2. k3s-agent service is running on the node: + ```bash + ssh root@ 'systemctl status k3s-agent' + ``` +3. Connectivity to control plane: + ```bash + ssh root@ 'curl -k https://10.42.1.1:6443' + ``` + +### Internal IP Shows Public IP Instead of Private IP + +**Cause:** Node not properly connected to Hetzner private network. + +**Fix:** +1. Verify network attachment in Hetzner Cloud Console +2. Ensure private network interface is up: + ```bash + ssh root@ 'ip addr show' + ``` +3. May need to recreate the VM with network properly attached + +### k3s Version Mismatch + +**Cause:** Provisioning script couldn't detect cluster version, or manual installation was attempted. + +**Fix:** +1. Cordon the node: + ```bash + kubectl cordon k3s-worker-N + ``` +2. Get correct version: + ```bash + CLUSTER_VERSION=$(kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.kubeletVersion}' | sed 's/v//') + ``` +3. Reinstall with correct version: + ```bash + K3S_TOKEN=$(ssh root@178.105.17.239 'cat /var/lib/rancher/k3s/server/node-token') + ssh root@ "curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION='v${CLUSTER_VERSION}' sh -s - agent \ + --server https://10.42.1.1:6443 \ + --token '${K3S_TOKEN}'" + ``` +4. Uncordon: + ```bash + kubectl uncordon k3s-worker-N + ``` + +--- + +## Reference Links + +- [K3S_OPERATIONS.md](./K3S_OPERATIONS.md) - Full operational guide +- [CLUSTER_ACCESS.md](./CLUSTER_ACCESS.md) - Access and authentication +- [Hetzner Cloud Docs](https://docs.hetzner.com/cloud/) +- [k3s Documentation](https://docs.k3s.io/) + +--- + +**Last Updated:** 2026-07-26 +**Maintained By:** CTO Agent diff --git a/infrastructure/CP1_UPDATE_PROCEDURE.md b/infrastructure/CP1_UPDATE_PROCEDURE.md new file mode 100644 index 0000000..c529954 --- /dev/null +++ b/infrastructure/CP1_UPDATE_PROCEDURE.md @@ -0,0 +1,14 @@ +# k3s-cp-1 OS Update Procedure — SUPERSEDED + +**Superseded by:** [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md) (DEV-515, 2026-08-23). + +**Why:** this document was written when cp-1 was the **sole** control-plane node and the datastore was embedded SQLite via kine. After [DEV-510](/DEV/issues/DEV-510) the cluster runs HA embedded etcd across `k3s-cp-1`, `k3s-cp-2`, `k3s-cp-3`, and all three CPs are `NoSchedule`-tainted (they host no StatefulSets or single-replica Deployments anymore). The single-CP kine cascade risk, the batched stateful eviction dance, and the external-`/livez` polling pattern no longer describe the current cluster. + +The generalized procedure covers cp-1 and cp-2 and cp-3. Use it. + +**Scripts:** + +- `infrastructure/scripts/os-update/update-cp-node.sh ` — the HA-aware entry point. +- `infrastructure/scripts/os-update/update-cp-1.sh` — thin wrapper (calls `update-cp-node.sh k3s-cp-1 "$@"`). Kept for backwards compatibility with any existing runbook that names it explicitly. + +**History note:** the original phase design (add swap → preflight → batched stateful eviction → drain → apt → reboot → finalize) is preserved in git history at commit `19af691` and `989033d` for future reference. The new procedure retains Phases A/B/D/E/F essentially unchanged and drops Phase C (batched stateful eviction) as obsolete. diff --git a/infrastructure/CP_UPDATE_PROCEDURE.md b/infrastructure/CP_UPDATE_PROCEDURE.md new file mode 100644 index 0000000..97185d9 --- /dev/null +++ b/infrastructure/CP_UPDATE_PROCEDURE.md @@ -0,0 +1,298 @@ +# HA k3s Control-Plane OS Update Procedure + +**Purpose:** apply Ubuntu OS updates (kernel, security, apt) to any control-plane node in the HA k3s cluster (`k3s-cp-1`, `k3s-cp-2`, `k3s-cp-3`) without losing etcd quorum and without a longer api-server outage than a normal reboot. + +**Audience:** the CTO agent, or an operator with root SSH to the cluster. Execution is board-approval-gated — see `## Approval gates` below. + +**Supersedes:** the earlier single-CP `CP1_UPDATE_PROCEDURE.md` (kept as a redirect stub). The cp-1-only procedure was written when cp-1 was the sole api-server and the datastore was embedded SQLite via kine. After [DEV-510](/DEV/issues/DEV-510) (2026-08-22), the cluster runs HA embedded etcd across cp-1/cp-2/cp-3 and all three CPs are `NoSchedule`-tainted, so: + +- **The kine cascade is no longer the driving risk.** The datastore is etcd 3.6.12 with 3-node quorum; a single CP drain no longer leaves a lone SQLite writer. The kubectl-latency guardrail stays as a sanity check but is now a much softer signal. +- **CPs host no StatefulSet or single-replica Deployment workloads.** The pre-drain stateful eviction dance from the old cp-1 doc no longer applies — CPs only host DaemonSets + `metrics-server` (currently on cp-2) + occasional CronJob completions. +- **The api-server is not lost on reboot.** During a CP reboot the other two apiservers keep serving. `/livez` is polled from the *other* apiservers, not from an external machine forced to poll the one being rebooted. + +--- + +## Scope + +**In scope** + +- Add a durable ≥ 2 GiB swapfile on any CP that has zero swap (idempotent one-off — `--add-swap`). +- k3s etcd snapshot as the restore point. +- `kubectl cordon` + `kubectl drain` on the target CP. +- `apt-get update/upgrade/dist-upgrade/autoremove` on the target CP. +- Controlled reboot with etcd-quorum-aware liveness monitoring. +- Uncordon + cluster health verify. + +**Out of scope — do NOT do here** + +- Any change to `/etc/rancher/k3s/*`, `/etc/systemd/system/k3s*.service*`, or the k3s binary version. k3s upgrades go through system-upgrade-controller (see `K3S_OPERATIONS.md`). +- Any change to manifests under `apps/`, `infrastructure/`, or applied via ArgoCD. +- Deleting PVs / PVCs. Any pod that gets rescheduled off the target CP stays on its new node. +- Fixing application-level problems. +- Rebuilding the node — if a CP does not return after reboot, escalate; the rebuild path is `ADD_WORKER_NODE.md` plus board approval, not this document. + +--- + +## Approval gates + +This procedure has **two** independent gates. Neither happens without explicit board approval on the corresponding Paperclip issue: + +1. **Add swap (Phase A).** Non-invasive, non-state-mutating, kubelet already runs with `failSwapOn=false`. Requires board approval per stateful-service safety rules because it modifies a CP node. +2. **Full OS update (Phases B–E).** Requires board approval because it drains + reboots one of the etcd members. Do NOT execute without an explicit `request_board_approval` acceptance on the execution ticket. + +Both gates are independent — swap can (and should) be added first, in a quiet window, before the full update is scheduled. + +--- + +## CP ordering rule (multiple CPs in one cycle) + +- **One at a time. Never two CPs cordoned or draining at once.** Two of three CPs down = etcd quorum loss = api-server unavailability for the whole cluster. +- **Leader last.** Query the current etcd leader before starting; update the two followers first (in any order), then the leader. Rationale: draining a follower is a no-op for the raft leader; draining the leader forces a re-election. Doing followers first minimises leader flapping. +- **Health gate between CPs.** After each CP finishes Phase F (finalize) and cluster health is green, wait at least `POST_UNCORDON_WAIT_SECONDS` (default 180 s) and re-check etcd endpoint status before touching the next CP. This gives etcd time to fully re-sync the just-rebooted member. +- **Halt on any yellow.** Any of the following aborts the cycle at the current CP (do NOT proceed to the next CP): etcd reports a member as not `started`, `kubectl get nodes` > 5 s, any node not `Ready`, any workload deployment/sts under desired replicas. + +Query the leader: + +```bash +ssh root@$CP1_HOST bash -c ' +ETCDCTL_API=3 etcdctl \ + --endpoints=https://127.0.0.1:2379 \ + --cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \ + --cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \ + --key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key \ + endpoint status --cluster -w table' +``` + +--- + +## Current CP workload (snapshot 2026-08-23) + +Re-derive live before any execution — this table is a design reference only. + +| Node | Kernel | Swap | StatefulSets | Single-replica Deployments | DaemonSets | +|------|--------|------|--------------|-----------------------------|------------| +| k3s-cp-1 | 6.8.0-137 | **0 B** | 0 | 0 | node-exporter, promtail, hcloud-csi-node, svclb-* | +| k3s-cp-2 | 6.8.0-137 | **0 B** | 0 | metrics-server (evictable) | node-exporter, promtail, hcloud-csi-node, svclb-* | +| k3s-cp-3 | 6.8.0-137 | **0 B** | 0 | 0 | node-exporter, promtail, hcloud-csi-node, svclb-* | + +etcd datastore live snapshot: 38 MiB per member, all three `started`, cp-3 is current leader. + +Because CPs are `NoSchedule`-tainted, no workload will end up on them via normal scheduling — the pre-drain stateful redistribution loop from the old cp-1 procedure is obsolete. + +--- + +## Phase A — Add swap (one-off, idempotent) + +**Goal:** eliminate the "3.7 GiB, no swap" underlying constraint on any CP before ever draining it. All three CPs currently have zero swap; add swap once per CP. + +**Preconditions:** + +- kubelet in this k3s already runs with `failSwapOn=false` (confirmed on cp-1 via `/api/v1/nodes/k3s-cp-1/proxy/configz`) — enabling swap does NOT break the kubelet. +- Target CP's `/` has ≥ `2 * SWAP_SIZE_MB` free. +- Board approval on the swap-add ticket. + +**Sizing:** default **4 GiB** swap. Rationale — cp-1 baseline (k3s + hosted daemons) sits at ~2 GiB used; 4 GiB swap gives us headroom for the drain-eviction transient without inflating disk usage past ~10 % of `/`. Minimum acceptable per guardrail: 2 GiB. + +**Location:** `/swapfile` (root filesystem). Not a separate partition — reversible, no LVM changes. + +**Steps (encoded in `update-cp-node.sh --add-swap`):** identical to the old cp-1 procedure — `fallocate` → `mkswap` → `swapon` → `/etc/fstab` → `vm.swappiness=10` in `/etc/sysctl.d/99-k3s-swap.conf`. Idempotent: skips if a swapfile of the target size is already active. Full script source in `scripts/os-update/update-cp-node.sh`. + +**Phase A also installs `etcd-client`** on the target CP if `etcdctl` is missing. This is a hard requirement for the etcd-quorum probes used in Phase B/E when the CP is later itself the update target and one of the *other* CPs must query etcd cluster status. cp-1 already has `etcdctl` (installed pre-HA); cp-2 and cp-3 pick it up here. + +**Rollback for Phase A:** `swapoff /swapfile && rm /swapfile` and remove the fstab line. Safe at any time — swap is a soft resource. + +**Verification after Phase A:** + +- `free -h` shows `Swap: 4.0Gi` used ≈ 0. +- `swapon --show` shows `/swapfile 4G`. +- `sysctl vm.swappiness` returns `10`. +- kubelet still Ready (`kubectl get node `). +- No new `MemoryPressure` condition. + +--- + +## Phase B — Preflight + +Runs from the operator machine (or from any healthy CP). Every command's stdout+stderr goes to `/tmp/os-update--.log` on the operator machine. Attach that log to the Paperclip execution ticket at the end. + +Run `update-cp-node.sh --preflight`. Checks: + +1. **Cluster is currently healthy:** `cluster-health.sh` returns 0. +2. **Target node is a CP:** `kubectl get node ` carries the `node-role.kubernetes.io/control-plane` label. +3. **All 3 CPs are healthy etcd members:** `etcdctl endpoint status --cluster` shows every member `started`, no errors. +4. **Only one CP is being updated this cycle:** no other CP is currently cordoned. +5. **Target CP has swap on:** abort if `free -h` shows `Swap: 0B`. Run `--add-swap` first. +6. **kubectl latency probe:** `time kubectl get nodes` returns in ≤ 5 s. +7. **etcd snapshot** (restore point): `k3s etcd-snapshot save --name pre-cp-os-update--` executed on the target CP. +8. **Records the current etcd leader** — if the target *is* the leader, prints a warning ("prefer updating a follower first"). Does NOT auto-swap the target; the operator/agent makes the call per the CP ordering rule. + +If any preflight check fails: STOP. Do not proceed. + +--- + +## Phase C — Cordon + drain the target CP + +CPs are `NoSchedule`-tainted, so cordon is mostly a belt-and-suspenders measure. Drain evicts the small tail of not-DaemonSet workloads (`metrics-server` currently lives on cp-2). + +```bash +kubectl cordon +kubectl drain \ + --ignore-daemonsets \ + --delete-emptydir-data \ + --timeout="${DRAIN_TIMEOUT_SECONDS:-600}s" +``` + +If drain reports a PDB block: do NOT `--force`. Uncordon the node, mark the run `blocked` on the PDB, and escalate. This is a workload PDB bug — fix separately. + +**Kine-cascade guardrail (softened for HA etcd):** during drain, `time kubectl get nodes` should still return in ≤ 5 s. Because we now have 3-member etcd (not lone SQLite kine), a single-CP drain does not create the write-amplification hazard from [DEV-495](/DEV/issues/DEV-495). But `> 5 s` still indicates something is wrong (etcd slow disk, leader flapping) — halt and investigate. + +--- + +## Phase D — apt on the target CP + +Identical to the worker-node apt block (`update-node.sh` step 3). `update-cp-node.sh --apt` runs this via `ssh root@ bash -s`: + +```bash +export DEBIAN_FRONTEND=noninteractive +APT_OPTS='-y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' +uname -r > /root/pre-apt-kernel +dpkg-query -W -f='${Package}\t${Version}\n' > /root/pre-apt-packages.tsv + +if dpkg --audit | grep -qE .; then dpkg --configure -a || true; fi +apt-get update +apt-get $APT_OPTS upgrade || { apt-get $APT_OPTS -f install; apt-get $APT_OPTS upgrade; } +apt-get $APT_OPTS dist-upgrade +apt-get $APT_OPTS autoremove --purge +apt-get clean +[ -f /var/run/reboot-required ] && echo REBOOT_REQUIRED=yes || echo REBOOT_REQUIRED=no +``` + +`pre-apt-kernel` and `pre-apt-packages.tsv` are the local rollback references — see Rollback below. + +--- + +## Phase E — Reboot handling (etcd-quorum-aware) + +**Duration expectation:** 90–180 s of *this member's* api-server unavailability. The other two apiservers keep serving; the operator machine's kubectl continues to work via one of them. + +**Escalation trigger:** rebooted apiserver not back on `/livez` after **10 minutes** → escalate. First check the Hetzner console via `hcloud server describe ` for boot state; if kernel-panic / initramfs, use grub previous-kernel path (see Rollback). Do NOT rebuild the node. + +**Steps (executed by `update-cp-node.sh --reboot`):** + +```bash +# 1. Reboot the target CP (ssh will hang up mid-command — expected). +ssh $SSH_OPTS root@$TARGET_HOST 'systemctl reboot' || true +sleep 15 + +# 2. Poll the TARGET's /livez from the operator machine. It is served on port 6443. +# Insecure (`-k`) because the server cert is self-signed by k3s. +deadline=$(( $(date +%s) + ${REBOOT_MAX_WAIT_SECONDS:-600} )) +while [ $(date +%s) -lt $deadline ]; do + code=$(curl -sk -o /dev/null -w '%{http_code}' https://$TARGET_HOST:6443/livez 2>/dev/null || echo 000) + [ "$code" = "200" ] && break + sleep 5 +done + +# 3. Verify etcd membership from a peer CP — the target should be back as `started`. +ssh $SSH_OPTS root@$PEER_HOST ' + ETCDCTL_API=3 etcdctl \ + --endpoints=https://127.0.0.1:2379 \ + --cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \ + --cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \ + --key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key \ + endpoint status --cluster -w table' + +# 4. Wait for kubelet Ready on the target from the api-server view. +deadline=$(( $(date +%s) + 300 )) +while [ $(date +%s) -lt $deadline ]; do + ready=$(kubectl get node "$TARGET_NODE" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo Unknown) + [ "$ready" = "True" ] && break + sleep 5 +done +``` + +**What to do if the api-server takes 3–10 minutes:** it may be normal for a large etcd on a slow disk. Wait it out. Do NOT restart k3s to "help" — an etcd member catching up on raft log is stateful and interrupting it can slow the whole cluster. + +**What to do if it takes > 10 minutes:** escalate; run the Hetzner-console diagnosis; if the console shows a bootable Ubuntu but the k3s service is failing, that's the boundary — this procedure stops here. Follow `K3S_OPERATIONS.md` for the k3s recovery path. + +--- + +## Phase F — Uncordon + verify + finalize + +```bash +kubectl uncordon +sleep "${POST_UNCORDON_WAIT_SECONDS:-180}" +RETRY_ON_TRANSIENT=1 infrastructure/scripts/os-update/cluster-health.sh +ssh root@ 'zgrep -h "Commandline\|Install\|Upgrade\|Remove" /var/log/apt/history.log* 2>/dev/null | tail -60' +``` + +Between CPs (when multiple CPs will be updated in the same cycle), also verify: + +```bash +ssh root@ ' + ETCDCTL_API=3 etcdctl \ + --endpoints=https://127.0.0.1:2379 \ + --cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \ + --cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \ + --key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key \ + endpoint status --cluster -w table' +``` + +All three members must be `started`, no errors, DB sizes within 10 % of each other. + +Attach the `/tmp/os-update--.log` to the execution ticket. Mark the ticket `done` on green health, or `blocked` naming the specific residual issue. + +--- + +## Rollback / recovery + +### If apt broke a package +- On the target CP, `dpkg --audit` to find half-configured packages. +- `dpkg --configure -a`, then `apt-get -f install`. +- If a specific package broke and you know the previous version from `/root/pre-apt-packages.tsv`, `apt-get install =`. + +### If the new kernel does not boot +- Hetzner cloud console: `hcloud server request-console ` → get VNC URL, watch boot. +- If grub is up, select the previous-kernel entry. Ubuntu keeps ≥ 1 old kernel installed by default. +- Once booted on the old kernel, `apt-get remove` the broken kernel and pin the working one: + ```bash + apt-mark hold linux-image- linux-headers- + ``` +- Escalate on the ticket regardless — a kernel rollback is a follow-up investigation, not a "done" outcome. + +### If the node does not return at all +- Do NOT `hcloud server delete`. Do NOT re-provision. +- Escalate to the board with the Hetzner console output and `/tmp/os-update--*.log`. +- The `--cluster-reset --cluster-reset-restore-path=` recovery path exists (see `K3S_OPERATIONS.md`) but requires board approval per stateful-service safety rules. The pre-flight snapshot from Phase B is the restore point. +- **HA-etcd advantage:** if one CP is permanently lost but the other two are healthy, you have quorum. Do not rush a `cluster-reset`. Follow the "member remove + re-add" path in the k3s docs first. + +### If etcd quorum degrades mid-cycle +- Two of three CPs down = write outage until quorum returns. Do NOT attempt to update the third. +- Uncordon all CPs, restore the just-updated one first, and confirm all three members `started` before doing anything else. + +--- + +## Automation entry points + +- `infrastructure/scripts/os-update/update-cp-node.sh [phase-flag]` — the generalized HA-aware flow. + - `--add-swap` — Phase A only (idempotent, safe standalone). + - `--dry-run` — walk all phases printing what would be done, no state change. + - `--preflight` — Phase B only. + - `--drain` — Phase C only (cordon + drain). + - `--apt` — Phase D apt commands only. + - `--reboot` — Phase E only. + - `--finalize` — Phase F only. + - `--run` — all phases with a confirmation between each unless `ASSUME_YES=1`. + +- `infrastructure/scripts/os-update/update-cp-1.sh` — retained as a thin wrapper that calls `update-cp-node.sh k3s-cp-1 "$@"`. Historical callers keep working. + +The script follows the same log-dir contract as `update-node.sh` (`/tmp/os-update--.log`). + +--- + +## Change history + +| Date | Change | By | +|------|--------|-----| +| 2026-08-16 | Initial cp-1-only procedure (DEV-496) | CTO agent | +| 2026-08-23 | Generalized to HA (cp-1/cp-2/cp-3); dropped Phase C batched stateful eviction (CPs are NoSchedule-tainted now); softened kine cascade guardrail (embedded etcd 3-member quorum); added CP ordering rule (leader last, one at a time); introduced `update-cp-node.sh` (DEV-515) | CTO agent | diff --git a/infrastructure/K3S_OPERATIONS.md b/infrastructure/K3S_OPERATIONS.md index 741a14e..6ce49ae 100644 --- a/infrastructure/K3S_OPERATIONS.md +++ b/infrastructure/K3S_OPERATIONS.md @@ -3,9 +3,9 @@ ## Current Cluster Status - **Version:** v1.36.2+k3s1 -- **Last Updated:** 2026-07-06 -- **Control Plane:** k3s-cp-1 (178.105.17.239 / 10.42.1.1) -- **Workers:** k3s-worker-1, k3s-worker-2, k3s-worker-3, k3s-update-runner +- **Last Updated:** 2026-08-30 +- **Control Plane:** k3s-cp-1 (178.105.17.239 / 10.42.1.1), k3s-cp-2 (188.245.85.199 / 10.42.1.8), k3s-cp-3 (49.13.92.162 / 10.42.1.9) — etcd HA +- **Workers:** k3s-worker-1, k3s-worker-2, k3s-worker-3, k3s-worker-4 (nbg1), k3s-worker-5, k3s-worker-6, k3s-update-runner - **Update Method:** k3s system-upgrade-controller (automated) ## Quick Reference @@ -79,12 +79,15 @@ The script will: ```bash ssh root@ "curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION='v${CLUSTER_VERSION}' sh -s - agent \ --server https://10.42.1.1:6443 \ - --token '${K3S_TOKEN}'" + --token '${K3S_TOKEN}' \ + --node-ip " ``` + **Critical:** The `--node-ip=` flag is required. Without it, k3s defaults to the public IP as the node's InternalIP, which breaks LoadBalancer service routing (see DEV-224). Verify with `kubectl get nodes -o wide` — the INTERNAL-IP column MUST show the private (10.42.1.x) IP. + 5. **Verify node joined:** ```bash - kubectl get nodes + kubectl get nodes -o wide # INTERNAL-IP must be private (10.42.1.x) kubectl get node -o yaml | grep flannel ``` @@ -410,13 +413,20 @@ failed to register flannel network: failed to acquire lease: subnet "X.X.X.X/16" ### Node CIDRs -| Node | Pod CIDR | Private IP | Public IP | -|------|----------|-----------|-----------| -| k3s-cp-1 | 10.244.0.0/24 | 10.42.1.1 | 178.105.17.239 | -| k3s-worker-1 | 10.244.1.0/24 | 10.42.1.2 | - | -| k3s-worker-2 | 10.244.2.0/24 | 10.42.1.3 | - | -| k3s-update-runner | 10.244.3.0/24 | 167.233.79.65 | 167.233.79.65 | -| k3s-worker-3 | 10.244.5.0/24 | 10.42.1.5 | 167.233.121.121 | +| Node | Private IP | Public IP | Datacenter | Role | +|------|-----------|-----------|------------|------| +| k3s-cp-1 | 10.42.1.1 | 178.105.17.239 | fsn1 | control-plane, etcd | +| k3s-cp-2 | 10.42.1.8 | 188.245.85.199 | fsn1 | control-plane, etcd | +| k3s-cp-3 | 10.42.1.9 | 49.13.92.162 | fsn1 | control-plane, etcd | +| k3s-worker-1 | 10.42.1.2 | 178.105.216.48 | fsn1 | worker | +| k3s-worker-2 | 10.42.1.3 | 49.13.134.255 | fsn1 | worker | +| k3s-worker-3 | 10.42.1.5 | 167.233.121.121 | fsn1 | worker | +| k3s-worker-4 | 10.42.1.6 | 128.140.3.80 | nbg1 | worker (stateless workloads only) | +| k3s-worker-5 | 10.42.1.7 | 167.233.192.86 | fsn1 | worker (DEV-410) | +| k3s-worker-6 | 10.42.1.10 | 49.12.223.133 | fsn1 | worker (cx33 4vCPU/8GB) | +| k3s-update-runner | 167.233.79.65 | 167.233.79.65 | fsn1 | update-runner | + +**Datacenter note:** Hetzner Cloud Volumes are datacenter-scoped. Pods with PersistentVolumes only schedule on nodes in the same datacenter as the PV. Provision stateful-workload workers in **fsn1** to match the existing PV pool. k3s-worker-4 remains in nbg1 and is only usable for stateless workloads. ### Critical Firewall Rule @@ -428,6 +438,7 @@ failed to register flannel network: failed to acquire lease: subnet "X.X.X.X/16" | Date | Version | Changes | By | |------|---------|---------|-----| +| 2026-08-30 | v1.36.2+k3s1 | DEV-410: Added k3s-worker-5 (fsn1) to unblock Stalwart scheduling; k3s-worker-4 (nbg1) retained for stateless workloads; provision script now sets --node-ip; refreshed node table with cp-2/cp-3 and worker-4/5/6 | CTO Agent | | 2026-07-06 | v1.36.2+k3s1 | Fixed DNS service CIDR mismatch (kubelet cluster-dns configuration), corrected Service CIDR documentation to 10.96.0.0/16, added DNS configuration guide | CTO Agent | | 2026-07-06 | v1.36.2+k3s1 | Fixed API server instability by adding explicit --cluster-cidr=10.244.0.0/16 to k3s service, updated all nodes to v1.36.2+k3s1 | CTO Agent | | 2026-07-06 | v1.36.2+k3s1 | Initial documentation, upgraded from v1.35.5, installed system-upgrade-controller | CTO Agent | diff --git a/infrastructure/OS_UPDATE_PROCEDURE.md b/infrastructure/OS_UPDATE_PROCEDURE.md new file mode 100644 index 0000000..56b2342 --- /dev/null +++ b/infrastructure/OS_UPDATE_PROCEDURE.md @@ -0,0 +1,362 @@ +# Weekly Rolling Ubuntu OS Update Procedure + +**Purpose:** Keep every k3s node's Ubuntu OS patched (kernel, security, package updates) with a **rolling, zero-downtime** update — one node at a time, drain → update → reboot → verify → uncordon → cluster health check → next node. Since [DEV-553](/DEV/issues/DEV-553) (2026-08-30) this covers **workers AND control-plane nodes** in a single cycle; workers run first, then CPs one at a time with etcd leader last. + +**Audience:** Agents (currently CTO, optionally a dedicated ClusterOps agent). Also usable manually by an operator. + +**Scope — what this procedure does:** +- Runs `apt-get update`, `apt-get -y upgrade`, `apt-get -y dist-upgrade`, `apt-get -y autoremove` on each cluster node. +- Reboots the node if a reboot is required (kernel/libc updates). +- Drains and cordons each node before touching it, uncordons after verification. +- Verifies the node and cluster are healthy before moving on. + +**Scope — what this procedure MUST NOT do:** +- **Never change the Kubernetes / k3s setup.** Do not touch `/etc/systemd/system/k3s*.service*`, `/etc/rancher/k3s/*`, k3s binary version, k3s config, kube-system manifests, network policies, or any manifest under `infrastructure/`, `apps/`, or applied via ArgoCD. +- **Do not upgrade k3s** here. k3s upgrades are handled separately by system-upgrade-controller (see `K3S_OPERATIONS.md` and `k3s-upgrade/`). +- **Do not delete PersistentVolumes, PVCs, or workload manifests.** +- Do not "fix" application-level problems on a node — that's out of scope. Only fix problems in the OS/apt/reboot layer of the current node being updated. Anything else → stop, report, escalate. + +--- + +## Cluster topology (context) + +| Role | Node | Private IP | Public IP | Datacenter | Notes | +|------|------|-----------|-----------|------------|-------| +| control-plane | k3s-cp-1 | 10.42.1.1 | 178.105.17.239 | fsn1 | included via `update-cp-node.sh` — see [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md) | +| control-plane | k3s-cp-2 | — | 188.245.85.199 | fsn1 | included via `update-cp-node.sh` — see [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md) | +| control-plane | k3s-cp-3 | — | 49.13.92.162 | fsn1 | included via `update-cp-node.sh` — see [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md) | +| worker | k3s-worker-1 | 10.42.1.2 | (via CP) | fsn1 | | +| worker | k3s-worker-2 | 10.42.1.3 | (via CP) | fsn1 | | +| worker | k3s-worker-3 | 10.42.1.5 | 167.233.121.121 | fsn1 | | +| worker | k3s-worker-4 | 10.42.1.6 | 128.140.3.80 | nbg1 | | +| worker | k3s-worker-5 | 10.42.1.7 | 167.233.192.86 | fsn1 | | +| runner | k3s-update-runner | 167.233.79.65 | 167.233.79.65 | fsn1 | k3s-upgrade helper, still an updatable node | + +Always re-derive the live list before running — nodes may have been added/removed: + +```bash +ssh root@178.105.17.239 'kubectl get nodes -o wide' +``` + +**Order rule:** update workers first, then control-plane nodes. Within each group, update in this order to protect stateful workloads: + +Workers: +1. runner + workers that host no PVs (safest — lowest disruption) +2. remaining workers +3. **Stalwart-hosting fsn1 workers last among workers** — Stalwart has hard fsn1 affinity, so draining a fsn1 worker while another fsn1 worker is also unavailable can leave Stalwart Pending. Never have two fsn1 workers cordoned/down at the same time. + +Control planes (integrated with the weekly cycle since [DEV-553](/DEV/issues/DEV-553); previously excluded and run manually via `CP_UPDATE_PROCEDURE.md`): +1. `os-update.sh` queries the current etcd leader before entering the CP phase. +2. Followers are updated first (in any order), leader last. Rationale: draining a follower is a no-op for raft leadership; draining the leader forces a re-election. Doing followers first minimises leader flapping. +3. Exactly one CP is drained or rebooting at a time — two CPs down means etcd quorum loss and a write outage across the cluster. +4. Between CPs `os-update.sh` re-runs `cluster-health.sh` and re-checks `etcdctl endpoint status --cluster`; any yellow signal halts the cycle at that CP. Settle wait between CPs is `POST_CP_WAIT_SECONDS` (default 180 s). +5. Each CP goes through `update-cp-node.sh --run` with `ASSUME_YES=1`: Phase A (add-swap, idempotent) → B preflight → C drain → D apt → E reboot with `/livez` polling and etcd rejoin check → F uncordon + cluster health. + +**Concurrency:** exactly one node at a time. Never in parallel. + +--- + +## Kine thundering-herd guardrails (added after 2026-08-16 incident, see DEV-495) + +**Historical context (single-CP kine era, 2026-08-16).** When the cluster ran a single CP with embedded SQLite via kine, draining a worker with many StatefulSets could trigger a self-amplifying eviction-and-slow-SQL cascade: kine backs up on writes → apiserver hangs → node-lease renewals fail → taint-eviction kicks in on more nodes → more writes → kine falls further behind → OOM risk on cp-1. + +**Current state (HA-etcd era, since DEV-510 on 2026-08-22).** The datastore is now embedded etcd 3.6.12 with 3-node quorum across cp-1/cp-2/cp-3. A single-worker drain no longer creates the lone-SQLite writer risk, and etcd tolerates one member being slow or briefly unreachable. Guardrails (1) and (3) below are retained as sanity checks but the failure mode they were named for is much less likely to trigger. Guardrail (2) is largely done (add swap on cp-1/cp-2/cp-3 tracked in the CP update procedure). + +All four rules below MUST be observed on every DEV-478 fire. The reference implementation (`os-update.sh`) does not yet enforce (1)/(3) automatically; the operator must actively watch. + +1. **Pre-plan drain order for StatefulSets.** Before draining any worker, `kubectl get pods -n -o wide` against every namespace with StatefulSets and count how many will be evicted from the target node. If a single drain would evict **more than 3 StatefulSets at once**, redistribute first: cordon+delete individual StatefulSet pods one namespace at a time and wait for each reschedule to settle before draining the whole node. +2. **All CPs must have swap before their update cycle.** cp-1/cp-2/cp-3 currently all have 0 swap. Add at least 2 GiB (default 4 GiB) of swap on each CP before its OS-update run — see Phase A in [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md). This is a one-off setup task per CP; once done it is a durable capability. +3. **Halt on kine slowness.** During any drain, keep a `time kubectl get nodes` running from cp-1. If it exceeds **5 s** in real time, halt the cycle immediately (uncordon the current node, do not proceed), verify cluster health, and escalate. The 5 s threshold is the leading indicator that kine has fallen behind and the taint-eviction cascade is about to start. +4. **CP updates are a separate design task.** With HA etcd across three CPs the risk is now etcd quorum loss (two CPs down simultaneously), not the sole-apiserver reboot. CP updates MUST be planned and board-approved per node; they are NOT covered by the standard `os-update.sh` cycle. See [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md) and `scripts/os-update/update-cp-node.sh` (DEV-515). The old cp-1-only doc `CP1_UPDATE_PROCEDURE.md` is a redirect stub. + +--- + +## Access + +Prereqs are the same as `CLUSTER_ACCESS.md`: +- SSH key for `root` on every node (jump via control plane for private-IP workers). +- `kubectl` available (either from the operator machine, or by SSH-ing to the control plane and using it there). +- `hcloud` CLI configured (only needed for firewall-related recovery — not for normal runs). + +Environment variables the scripts expect: +- `CONTROL_PLANE_HOST` — default `178.105.17.239` +- `CONTROL_PLANE_PRIVATE` — default `10.42.1.1` +- Drain timeout: `DRAIN_TIMEOUT_SECONDS` — default `600` +- Reboot wait: `REBOOT_MAX_WAIT_SECONDS` — default `600` +- Post-uncordon settle: `POST_UNCORDON_WAIT_SECONDS` — default `180` +- Post-uncordon bounded health retry (DEV-595): + - `HEALTH_MAX_RETRIES` — default `10` (extra attempts after the first) + - `HEALTH_RETRY_INTERVAL_SECONDS` — default `30` + - Short-circuits immediately on persistent pod errors + (`CrashLoopBackOff`, `ImagePullBackOff`, `ErrImagePull`, + `CreateContainerConfigError`, `InvalidImageName`). + +--- + +## Preflight (run once per cycle, before touching any node) + +1. **Cluster is currently healthy.** If any of the checks below fail, STOP and open an issue — do not start OS updates on an already-degraded cluster. + ```bash + ssh root@$CONTROL_PLANE_HOST bash -s <<'EOF' + set -e + kubectl get nodes + echo "--- Not-ready nodes:" + kubectl get nodes -o json | jq -r '.items[] | select(.status.conditions[] | select(.type=="Ready" and .status!="True")) | .metadata.name' + echo "--- Pods not Running/Completed:" + kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded | grep -v 'STATUS' || echo " (none)" + echo "--- Non-Ready pods (Running but not Ready):" + kubectl get pods -A -o json | jq -r '.items[] | select(.status.phase=="Running") | select([.status.conditions[]?|select(.type=="Ready")|.status]|contains(["False"])) | "\(.metadata.namespace)/\(.metadata.name)"' + EOF + ``` + Only proceed if: all nodes `Ready`, no non-Running/Succeeded pods, no Running-but-not-Ready pods (small transient counts are OK — retry once and continue if it clears). + +2. **Snapshot k3s datastore** (control plane only — this is a checkpoint you can restore etcd from if the control-plane reboot goes badly): + ```bash + ssh root@$CONTROL_PLANE_HOST 'k3s etcd-snapshot save --name pre-os-update-$(date +%Y%m%d)' + ssh root@$CONTROL_PLANE_HOST 'k3s etcd-snapshot list | tail -5' + ``` + +3. **List the nodes to update** and derive the ordered plan. Persist to `/tmp/os-update-plan.txt` on the control plane for auditability. See `scripts/os-update/os-update.sh` for the reference implementation of the ordering rule. + +--- + +## Per-node procedure + +Repeat for each node in the ordered plan. The reference implementation is `infrastructure/scripts/os-update/update-node.sh `; the manual steps below are what that script does. + +Throughout: every command's stdout+stderr goes to a per-run log at `/tmp/os-update--.log` on the control plane. Attach the log to the issue at the end. + +### 1. Pre-check the node + +```bash +NODE= +kubectl get node "$NODE" +kubectl describe node "$NODE" | grep -A2 Conditions +``` + +Confirm: `Ready=True`, not already cordoned, no `DiskPressure`/`MemoryPressure`/`PIDPressure`. + +### 2. Cordon and drain + +```bash +kubectl cordon "$NODE" +kubectl drain "$NODE" \ + --ignore-daemonsets \ + --delete-emptydir-data \ + --disable-eviction=false \ + --timeout="${DRAIN_TIMEOUT_SECONDS:-600}s" +``` + +If drain fails on a PodDisruptionBudget: +- Do NOT force-delete pods (breaks HA guarantees). +- Log the blocking PDB, uncordon the node, mark the node as `SKIPPED_PDB` in the plan, and continue with the next node. Escalate the PDB conflict on the issue at the end. + +If drain fails on a lone pod without a controller: +- Do NOT `--force`. Same as above — uncordon, mark `SKIPPED_ORPHAN_POD`, continue. + +### 3. Update the OS + +On the node itself: + +```bash +ssh -o StrictHostKeyChecking=accept-new root@ bash -s <<'REMOTE' +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +# Refresh package lists +apt-get update +# Configure apt to keep existing config files silently (no interactive prompts) +APT_OPTS='-y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' +apt-get $APT_OPTS upgrade +apt-get $APT_OPTS dist-upgrade +apt-get $APT_OPTS autoremove --purge +apt-get clean +# Report reboot need +if [ -f /var/run/reboot-required ]; then + echo "REBOOT_REQUIRED=yes" + cat /var/run/reboot-required.pkgs 2>/dev/null || true +else + echo "REBOOT_REQUIRED=no" +fi +REMOTE +``` + +For private-IP workers, target them from the control plane (`ssh -J root@$CONTROL_PLANE_HOST root@10.42.1.X`) or run the whole block after SSH-ing to the control plane first. + +Common OS-only fixes the agent MAY perform on the node if apt fails: +- `dpkg --configure -a` after an interrupted install +- `apt-get -f install` to resolve broken deps +- Free disk with `journalctl --vacuum-time=3d` or `apt-get clean` if `/` is full +- Restart a system service that is stuck (`systemctl restart `) — but NOT `k3s`, `k3s-agent`, `containerd`, `flanneld`, or any container runtime + +**Never**: change k3s config, delete PVs, uninstall packages the OS didn't schedule, edit `/etc/rancher/`, or reinstall k3s. If a fix would touch any of those, stop and escalate. + +### 4. Reboot if required + +If `REBOOT_REQUIRED=yes`: + +```bash +ssh root@ 'systemctl reboot' || true +# Wait until SSH responds again (max REBOOT_MAX_WAIT_SECONDS) +deadline=$(( $(date +%s) + ${REBOOT_MAX_WAIT_SECONDS:-600} )) +while [ $(date +%s) -lt $deadline ]; do + sleep 10 + if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new root@ 'uptime' 2>/dev/null; then + echo "Node back up"; break + fi +done +``` + +If the node doesn't come back within the timeout: escalate immediately. Do NOT rebuild the node or touch k3s — a rebuild requires the ADD_WORKER_NODE procedure and is a separate approved action. + +### 5. Wait for k3s-agent / k3s to be ready again + +```bash +# Wait for kubelet Ready condition (control plane view) +deadline=$(( $(date +%s) + 300 )) +while [ $(date +%s) -lt $deadline ]; do + READY=$(kubectl get node "$NODE" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') + [ "$READY" = "True" ] && break + sleep 5 +done +kubectl get node "$NODE" +``` + +If Ready never returns True: escalate. Do NOT change k3s config. + +### 5b. Reconcile Docker on labeled nodes (before uncordon) + +For nodes carrying the `basicstack.de/docker=true` label (currently the workers that run the Forgejo runner), ensure `docker.io` is present + running before scheduling resumes. Without this the runner pod comes back `ContainerCreating` because `hostPath` requires the docker socket to exist (see [DEV-498](/DEV/issues/DEV-498) and [DEV-499](/DEV/issues/DEV-499)). + +```bash +scripts/os-update/ensure-node-docker.sh "$NODE" +``` + +This step is a no-op on nodes without the label. `update-node.sh` runs it automatically between step 5 and step 6; the script is also safe to run ad-hoc after any manual OS operation. As a defensive extra measure, the apt phase (step 3) now runs `apt-mark manual docker.io` on any node where it is installed, so `apt-get autoremove --purge` cannot silently strip it. + +### 6. Uncordon + +```bash +kubectl uncordon "$NODE" +``` + +### 7. Post-node health verification + +Wait for pods to reschedule and settle, then verify: + +```bash +sleep "${POST_UNCORDON_WAIT_SECONDS:-180}" + +# All nodes Ready? +kubectl get nodes +kubectl get nodes -o json | jq -r '.items[] | select(.status.conditions[] | select(.type=="Ready" and .status!="True")) | .metadata.name' | grep . && { echo "NOT-READY NODES"; exit 1; } || true + +# Any pod not Running/Succeeded? +BAD=$(kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded --no-headers 2>/dev/null | wc -l) +[ "$BAD" -gt 0 ] && { echo "BAD PODS: $BAD"; kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded; exit 1; } || true + +# Any Deployment/StatefulSet under desired replicas? +kubectl get deploy -A -o json | jq -r '.items[] | select((.status.readyReplicas // 0) < (.spec.replicas // 1)) | "deploy \(.metadata.namespace)/\(.metadata.name) \(.status.readyReplicas // 0)/\(.spec.replicas)"' +kubectl get sts -A -o json | jq -r '.items[] | select((.status.readyReplicas // 0) < (.spec.replicas // 1)) | "sts \(.metadata.namespace)/\(.metadata.name) \(.status.readyReplicas // 0)/\(.spec.replicas)"' +``` + +Only proceed to the next node when ALL of the above are green. If not: +- Give it another 3 minutes and re-check (workloads with large images may still be pulling). +- If still not green: STOP the cycle. Uncordon everything, leave the cluster in a stable state, and open a follow-up issue with the failing workloads. Do NOT proceed to update more nodes. + +### 8. Control plane handling + +Since [DEV-553](/DEV/issues/DEV-553) (2026-08-30) the weekly `os-update.sh` cycle **includes** the three CPs (`k3s-cp-1`, `k3s-cp-2`, `k3s-cp-3`) after all workers finish. The CP steps are delegated to `scripts/os-update/update-cp-node.sh` — see [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md) for the full design. Key properties: + +- CPs are `NoSchedule`-tainted and host no StatefulSet workloads today, but they are the etcd quorum. Two of three CPs cordoned/rebooting at once = write outage. `os-update.sh` walks CPs sequentially with a health gate between each. +- `os-update.sh` queries `etcdctl endpoint status --cluster` before the CP phase to identify the current leader, then updates followers first and leader last. +- `update-cp-node.sh` polls the target's `/livez` from the operator machine and cross-checks `etcdctl endpoint status --cluster` from a peer CP to confirm the just-rebooted member rejoined etcd as `started`. +- Phase A (`--add-swap`) is idempotent: creates `/swapfile` (4 GiB default) with `vm.swappiness=10` on any CP without swap, and installs `etcd-client` if `etcdctl` is missing. Re-runs are no-ops when swap is already active. + +Skip flags on `os-update.sh`: +- `--skip-cps` — workers only (legacy behavior for narrow patch cycles). +- `--only-cps` — skip workers and only walk the CP phase. +- `--only ` — single node, routed automatically to worker or CP. + +--- + +## Finalization + +After all nodes are done: + +1. Final cluster health snapshot (same preflight commands as at the start). +2. Print apt history summary per node so the audit trail has "what changed": + ```bash + for host in ; do + echo "=== $host ===" + ssh root@$host 'zgrep -h "Commandline\|Install\|Upgrade\|Remove" /var/log/apt/history.log* 2>/dev/null | tail -60' + done + ``` +3. Optional: prune old etcd snapshots to keep disk in check: + ```bash + ssh root@$CONTROL_PLANE_HOST 'k3s etcd-snapshot list' + # delete anything older than 14 days if desired (manual review) + ``` +4. Attach the per-run logs to the Paperclip issue that triggered this run. +5. Update the issue with: + - Nodes updated (list) + - Nodes skipped (list + reason) + - Any package that required manual intervention + - Reboots performed + - Final `kubectl get nodes` output + +If everything is clean → mark the issue `done`. +If a node was skipped or errored → mark `blocked` with the unblock action, or open a child issue for the specific failure. + +--- + +## Recovery / what to do when it goes wrong + +**Node stuck cordoned after failure:** `kubectl uncordon ` — always leave the cluster back in its normal scheduling state. + +**Node fails to boot after reboot:** +- Check Hetzner console for boot errors (kernel panic, initramfs). +- Try `hcloud server reboot ` from `hcloud` CLI. +- If the node cannot recover: escalate. Do NOT delete or rebuild the Hetzner server without board approval; that is the ADD_WORKER_NODE flow. + +**k3s-agent won't start after reboot:** +- Check `journalctl -u k3s-agent -n 100`. +- Do NOT edit the k3s-agent unit file. Do NOT re-run the k3s installer. +- Escalate. This is out of scope for the OS-update procedure. + +**Pods CrashLoopBackOff after node came back:** +- Not an OS-update problem to fix — the node is healthy, apt succeeded. Leave the node uncordoned, stop the cycle, and open an application-level issue. + +**PDB blocked drain:** +- Never `--force` the drain. Leave the node uncordoned, skip it, note in the report which PDB blocked and which workload owns it. + +**Datastore snapshot restore (last resort — CP only):** +- Only if the control plane is broken beyond repair. See `K3S_OPERATIONS.md` for the `--cluster-reset --cluster-reset-restore-path=` procedure. Requires board approval — do NOT execute unattended. + +--- + +## Automation entry points + +- `infrastructure/scripts/os-update/os-update.sh` — full cycle runner (preflight → worker loop → CP loop → finalization). Idempotent, resumable via `--start-from ` (works for workers or CPs). Use `--dry-run` to print the plan without touching anything. Use `--skip-cps` / `--only-cps` to run only one half of the cycle. +- `infrastructure/scripts/os-update/update-node.sh ` — single-node update for **workers** (all 7 per-node steps). Callable standalone for retry. Never call this for a CP. +- `infrastructure/scripts/os-update/update-cp-node.sh ` — HA-aware CP update flow (add swap, preflight, drain, apt, reboot, finalize). Accepts `k3s-cp-1`, `k3s-cp-2`, or `k3s-cp-3`. See [`CP_UPDATE_PROCEDURE.md`](CP_UPDATE_PROCEDURE.md). +- `infrastructure/scripts/os-update/update-cp-1.sh` — thin wrapper (calls `update-cp-node.sh k3s-cp-1 "$@"`). Kept for backwards compat. +- `infrastructure/scripts/os-update/cluster-health.sh` — the preflight/post-node health check as a standalone command; exits non-zero on any failure. + +Read the script sources for the exact behavior before running them. They mirror this procedure step for step. + +--- + +## Weekly schedule + +A Paperclip routine (see `infrastructure/OS_UPDATE_ROUTINE.md`) fires this procedure once per week. The routine creates a task whose description points here. The assigned agent reads this document and executes the automation. + +--- + +## Change history + +| Date | Change | By | +|------|--------|-----| +| 2026-08-09 | Initial procedure + automation scripts | CTO agent (DEV-462) | +| 2026-08-16 | Added kine thundering-herd guardrails after DEV-495 incident (worker-3 drain caused CP kine cascade + near-OOM on cp-1). Four new rules: pre-plan StatefulSet drain order, swap on cp-1, halt on kubectl >5 s, cp-1 update as separate design task. | CTO agent (DEV-478/DEV-495) | +| 2026-08-23 | Downgraded the kine cascade section for the HA-etcd era (DEV-510 migrated cp-1 from kine SQLite to embedded etcd, cp-2/cp-3 joined as full members). Pointed §8 and the "Automation entry points" list at the new HA-aware `CP_UPDATE_PROCEDURE.md` / `update-cp-node.sh`. Old `CP1_UPDATE_PROCEDURE.md` and `update-cp-1.sh` are now redirect stubs. | CTO agent (DEV-515) | +| 2026-08-30 | Integrated the three CPs into the weekly `os-update.sh` cycle (leader-last ordering, health gate between CPs, delegates each CP to `update-cp-node.sh --run` with `ASSUME_YES=1`). Added `--skip-cps` / `--only-cps` flags. `--only ` and `--start-from ` now route between worker/CP lists automatically. | CTO agent (DEV-553) | diff --git a/infrastructure/OS_UPDATE_ROUTINE.md b/infrastructure/OS_UPDATE_ROUTINE.md new file mode 100644 index 0000000..1659aaa --- /dev/null +++ b/infrastructure/OS_UPDATE_ROUTINE.md @@ -0,0 +1,47 @@ +# Weekly OS Update — Paperclip Routine + +**Owner:** CTO agent (until a dedicated ClusterOps agent is spun up) +**Schedule:** every **Sunday 03:00 Europe/Berlin** (low-traffic window, before Monday operations) +**Companion procedure:** [`OS_UPDATE_PROCEDURE.md`](OS_UPDATE_PROCEDURE.md) + +## What the routine does + +Each fire creates a Paperclip task whose description points to `OS_UPDATE_PROCEDURE.md`. The assigned agent: + +1. Checks out the task. +2. Reads the procedure doc. +3. Runs `infrastructure/scripts/os-update/os-update.sh` end to end. +4. Attaches per-run logs (`/tmp/os-update-/`) to the task. +5. Closes the task `done` on success, or `blocked` with a named unblock action on skip/error. + +## Routine configuration (Paperclip) + +- `title`: "Weekly rolling OS update — k3s cluster nodes" +- `assigneeAgentId`: CTO (agent id `4b5f09a2-22d8-4e3d-9ac4-46d008ad1385`) +- `projectId`: `d24057b8-02ca-41cd-b7a0-6151298c6e2c` (BasicStack Phase-2) +- `goalId`: `b4dfe4a8-5c37-4f62-aad9-fa340dcd34a4` (self-hosted Kubernetes at Hetzner) +- `priority`: `medium` +- `concurrencyPolicy`: `coalesce_if_active` (a previous run still open? merge into it, don't stack) +- `catchUpPolicy`: `skip_missed` (no back-fires if Paperclip was down) +- Trigger: `schedule` — cron `0 3 * * 0`, timezone `Europe/Berlin` + +The task description created by each fire is a link to the procedure doc in Forgejo, so the doc is the single source of truth even if the routine metadata drifts. + +## Reassigning to a dedicated agent + +If we later hire a ClusterOps agent, only two things change: + +1. `PATCH /api/routines/{routineId}` — update `assigneeAgentId`. +2. Nothing in `OS_UPDATE_PROCEDURE.md` or the scripts changes; the procedure is agent-neutral. + +## Pausing / disabling + +To pause without losing config: + +```bash +curl -X PATCH -H "Authorization: Bearer $PAPERCLIP_API_KEY" -H "Content-Type: application/json" \ + -d '{"status":"paused"}' \ + "$PAPERCLIP_API_URL/routines/{routineId}" +``` + +Resume with `{"status":"active"}`. diff --git a/infrastructure/README.md b/infrastructure/README.md index c33298d..e775ae7 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -2,6 +2,14 @@ This directory contains cluster-wide infrastructure configurations that support all applications. +## Operational procedures (agent-facing) + +- **[OS_UPDATE_PROCEDURE.md](OS_UPDATE_PROCEDURE.md)** — weekly rolling Ubuntu OS update for all cluster nodes (drain → apt → reboot → verify → uncordon → cluster health → next). Never touches k3s config. +- **[OS_UPDATE_ROUTINE.md](OS_UPDATE_ROUTINE.md)** — the Paperclip routine that fires the above weekly. +- **[K3S_OPERATIONS.md](K3S_OPERATIONS.md)** — k3s version upgrades (separate concern from OS updates). +- **[ADD_WORKER_NODE.md](ADD_WORKER_NODE.md)** — adding a worker node. +- **[CLUSTER_ACCESS.md](CLUSTER_ACCESS.md)** — SSH / kubectl access. + ## Structure ### `networking/` diff --git a/infrastructure/networking/DNS_REQUIREMENTS.md b/infrastructure/networking/DNS_REQUIREMENTS.md index 730ea6e..7160d3c 100644 --- a/infrastructure/networking/DNS_REQUIREMENTS.md +++ b/infrastructure/networking/DNS_REQUIREMENTS.md @@ -39,6 +39,7 @@ All HTTP/HTTPS services are accessed via Traefik LoadBalancer. Point DNS records | passbolt.basicstack.de | A | 178.105.17.239 | Password Manager | 443 | | opencloud.basicstack.de | A | 178.105.17.239 | OpenCloud | 443 | | registry.basicstack.de | A | 178.105.17.239 | Container Registry | 443 | +| pangolin.basicstack.de | A | 178.105.17.239 | Pangolin (Plausible Analytics) | 443 | ### Mail-Specific DNS Records diff --git a/infrastructure/networking/NETWORK_ARCHITECTURE.md b/infrastructure/networking/NETWORK_ARCHITECTURE.md index f85df2f..8d45470 100644 --- a/infrastructure/networking/NETWORK_ARCHITECTURE.md +++ b/infrastructure/networking/NETWORK_ARCHITECTURE.md @@ -1,6 +1,6 @@ # BasicStack k3s Cluster Network Architecture -**Last Updated:** 2026-07-06 +**Last Updated:** 2026-08-02 **Cluster Version:** k3s v1.36.2+k3s1 ## Overview @@ -15,9 +15,16 @@ This document provides comprehensive documentation of the network configuration ┌──────────┴──────────┐ │ DNS (basicstack.de) │ │ *.basicstack.de │ + │ → 138.199.128.63 │ └──────────┬──────────┘ | ┌─────────────────┴─────────────────┐ + │ Hetzner Load Balancer │ + │ IP: 138.199.128.63 │ + │ Targets: k3s-cp-1 + workers 1-5 │ + └─────────────────┬─────────────────┘ + | + ┌─────────────────┴─────────────────┐ │ Hetzner Cloud (fw-k3s) │ │ Firewall: Port 80, 443, 25, │ │ 587, 465, 143, 993 │ @@ -198,27 +205,60 @@ Ports: ### Stalwart Mail Server +Stalwart uses a **two-tier load balancing architecture** combining Hetzner Cloud Load Balancer with Kubernetes LoadBalancer services. + +#### Architecture + +``` +External Mail Client + ↓ +Hetzner Cloud Load Balancer (Managed) + - Targets: k3s-cp-1, k3s-worker-1 through k3s-worker-5 + - Health checks enabled + ↓ +k3s Node NodePorts (auto-assigned by k3s ServiceLB) + ↓ +Kubernetes LoadBalancer Services (stalwart-smtp / stalwart-imap) + ↓ +Stalwart Pod (fsn1 datacenter, PV affinity) +``` + +#### Why This Architecture? + +The Kubernetes services **MUST** be type `LoadBalancer` (not NodePort) because: +- **Hetzner CCM Integration**: The Hetzner Cloud Controller Manager automatically configures the Hetzner Load Balancer when it detects a Kubernetes LoadBalancer service +- **Automatic Target Management**: CCM keeps the Hetzner LB targets synchronized with cluster node changes +- **Health Check Automation**: CCM configures health checks based on the service configuration +- **No Manual Intervention**: Changing to NodePort would require manual Hetzner LB management + +See DEV-439 for detailed investigation. + #### SMTP Service ```yaml Service: stalwart/stalwart-smtp -Type: LoadBalancer +Type: LoadBalancer # Required for Hetzner CCM integration External-IPs: 10.42.1.2, 10.42.1.3, 10.42.1.5, 178.105.17.239 Ports: - 25/TCP (SMTP) - 587/TCP (Submission) - - 465/TCP (SMTPS) ``` #### IMAP Service ```yaml Service: stalwart/stalwart-imap -Type: LoadBalancer +Type: LoadBalancer # Required for Hetzner CCM integration External-IPs: 10.42.1.2, 10.42.1.3, 10.42.1.5, 178.105.17.239 Ports: - - 143/TCP (IMAP) - - 993/TCP (IMAPS) + - 993/TCP (IMAPS) # Port 143 disabled per DEV-359 ``` +#### Historical Context + +- **Initial Setup**: Used k3s ServiceLB with hostPort bindings (DEV-359) +- **Migration**: Migrated to Hetzner Load Balancer for stability (DEV-357) +- **Current State**: Hetzner LB + Kubernetes LoadBalancer services (managed by Hetzner CCM) +- **Key Fix**: Resolved CNI-HOSTPORT orphaned rules issue by adding external LB layer + ## DNS Configuration ### Domain: basicstack.de @@ -228,23 +268,26 @@ Ports: #### Required DNS Records **A Records (HTTP/HTTPS Services):** -All HTTPS services point to LoadBalancer IPs. Since we use multiple LoadBalancer IPs, DNS should point to the most stable/accessible IPs: +All services point to the Hetzner Load Balancer IP address. -- Recommended: Point to worker node private IPs (10.42.1.2, 10.42.1.3, 10.42.1.5) OR -- Alternative: Point to control-plane public IP (178.105.17.239) +**Current Configuration:** +All domains and subdomains point to: **138.199.128.63** (Hetzner Load Balancer) **Wildcard Option:** ``` -*.basicstack.de → 178.105.17.239 (or round-robin to worker IPs) +*.basicstack.de → 138.199.128.63 ``` **Specific Records:** ``` -basicstack.org → 178.105.17.239 -www.basicstack.org → 178.105.17.239 -paperclip.basicstack.de → 178.105.17.239 -mail.basicstack.de → 178.105.17.239 -auth.basicstack.de → 178.105.17.239 +basicstack.org → 138.199.128.63 +www.basicstack.org → 138.199.128.63 +paperclip.basicstack.de → 138.199.128.63 +mail.basicstack.de → 138.199.128.63 +auth.basicstack.de → 138.199.128.63 +forgejo.basicstack.de → 138.199.128.63 +directus.basicstack.de → 138.199.128.63 +books.basicstack.de → 138.199.128.63 (etc. for all services) ``` @@ -391,23 +434,36 @@ Database and cache services (PostgreSQL, Redis) have restrictive policies that o ### Ingress Traffic (HTTP/HTTPS) ``` -Internet → DNS → Hetzner Firewall (fw-k3s) - → LoadBalancer IP (any worker node) - → Traefik Ingress Controller (Pod on control-plane) +Internet → DNS (→ 138.199.128.63) + → Hetzner Load Balancer + → k3s Node (k3s-cp-1 or k3s-worker-1 through k3s-worker-5) + → Hetzner Firewall (fw-k3s) + → NodePort (auto-assigned by k3s ServiceLB) + → Traefik Ingress Controller → Backend Service (ClusterIP) → Application Pods ``` -### Direct LoadBalancer Traffic (SMTP/IMAP) +### Mail Traffic (SMTP/IMAP) - Stalwart + +**Two-Tier Architecture** (Hetzner Load Balancer + Kubernetes LoadBalancer): ``` -Internet → DNS → Hetzner Firewall (fw-k3s) - → LoadBalancer IP (any worker or control-plane node) +Internet → DNS (mail.basicstack.de) + → Hetzner Cloud Load Balancer (managed by Hetzner CCM) + → k3s Node (any target: k3s-cp-1 or k3s-worker-1 through k3s-worker-5) + → NodePort (auto-assigned by k3s ServiceLB) + → Kubernetes LoadBalancer Service (stalwart-smtp / stalwart-imap) → Service DaemonSet Pod (svclb-stalwart-*) - → Backend Service (ClusterIP) - → Stalwart Pods + → Stalwart Pod (in fsn1 datacenter due to PV affinity) ``` +**Key Points:** +- Hetzner CCM automatically manages the Hetzner Load Balancer configuration +- Service type MUST be LoadBalancer (not NodePort) for CCM integration +- k3s ServiceLB creates the NodePorts that the Hetzner LB targets +- Traffic flows through two load balancing layers for reliability + ### Internal Service-to-Service ``` @@ -669,4 +725,5 @@ kubectl get certificate -n -w | Date | Change | Author | |------|--------|--------| +| 2026-08-02 | Document Hetzner Load Balancer architecture for Stalwart mail services | CTO (DEV-439) | | 2026-07-06 | Initial comprehensive network architecture documentation | CTO (DEV-225) | diff --git a/infrastructure/networking/traefik-helmchartconfig.yaml b/infrastructure/networking/traefik-helmchartconfig.yaml new file mode 100644 index 0000000..501aea5 --- /dev/null +++ b/infrastructure/networking/traefik-helmchartconfig.yaml @@ -0,0 +1,45 @@ +--- +# k3s built-in Traefik HelmChartConfig overrides. +# +# k3s ships with a Traefik HelmChart; this HelmChartConfig layers extra +# values on top without forking the chart. The helm-controller reconciles it +# and rolls Traefik whenever this changes. +# +# Not managed by ArgoCD (ArgoCD doesn't manage kube-system in this cluster); +# `kubectl apply -f infrastructure/networking/traefik-helmchartconfig.yaml` +# is the reproducibility path if the cluster is rebuilt. +# +# Change history: +# - DEV-455: badger plugin experimental + additionalArguments. +# - DEV-457: enable providers.kubernetesCRD.allowCrossNamespace so Pangolin's +# TraefikService (ns=pangolin) can reference the paperclip Service (ns=paperclip). +# Add certResolver `letsencrypt` (HTTP-01) with persistent acme.json backing so +# Pangolin-generated IngressRoutes with `tls.certResolver=letsencrypt` load. +# podSecurityContext.fsGroup=65532 makes /data writable by Traefik's non-root UID. +apiVersion: helm.cattle.io/v1 +kind: HelmChartConfig +metadata: + name: traefik + namespace: kube-system +spec: + valuesContent: |- + experimental: + plugins: + badger: + moduleName: "github.com/fosrl/badger" + version: "v1.5.0" + persistence: + enabled: true + size: 128Mi + path: /data + podSecurityContext: + fsGroup: 65532 + fsGroupChangePolicy: OnRootMismatch + additionalArguments: + - "--experimental.plugins.badger.moduleName=github.com/fosrl/badger" + - "--experimental.plugins.badger.version=v1.5.0" + - "--providers.kubernetescrd.allowCrossNamespace=true" + - "--certificatesresolvers.letsencrypt.acme.email=admin@basicstack.de" + - "--certificatesresolvers.letsencrypt.acme.storage=/data/acme.json" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" diff --git a/infrastructure/scripts/os-update/README.md b/infrastructure/scripts/os-update/README.md new file mode 100644 index 0000000..bcf86b3 --- /dev/null +++ b/infrastructure/scripts/os-update/README.md @@ -0,0 +1,52 @@ +# OS Update Automation + +Scripts that implement the weekly rolling Ubuntu OS-update procedure. + +**Authoritative doc:** [`../../OS_UPDATE_PROCEDURE.md`](../../OS_UPDATE_PROCEDURE.md) — read it first. The scripts here mirror that procedure step-for-step. + +## Files + +| Script | Purpose | +|--------|---------| +| `cluster-health.sh` | Non-zero if any node is not Ready, any pod is not Running/Ready, or any Deployment/StatefulSet is below its desired replica count. Used at preflight and after each node. | +| `update-node.sh ` | Drain, apt-update, reboot-if-required, wait for Ready, ensure Docker on labeled nodes, uncordon, post-node health check. Retriable per-node. | +| `ensure-node-docker.sh ` | Idempotent: on nodes labeled `basicstack.de/docker=true`, install docker.io if missing, `apt-mark manual`, enable+start the docker service, wait for `/var/run/docker.sock`, re-apply the label. No-op on nodes without the label. Called from `update-node.sh` between "kubelet Ready" and "uncordon"; also runnable ad-hoc for recovery. | +| `os-update.sh` | Full cycle runner: preflight → etcd snapshot → ordered per-node loop → finalization + apt history digest. | + +## Order of operations (encoded in `os-update.sh`) + +1. Workers with no stateful affinity concerns first. +2. `fsn1` workers (potential Stalwart hosts) last among workers — see [`stalwart-datacenter-affinity`](../../K3S_OPERATIONS.md) note. +3. `k3s-cp-1` last (single control plane). +4. One node at a time. Never in parallel. + +## Guardrails the scripts enforce + +- Preflight cluster health failure → refuse to start. +- SSH-drift preflight (DEV-587): before draining anything, verify the operator can SSH to every planned node and that cp-1's pubkey is present in each target's `/root/.ssh/authorized_keys`. Missing pubkeys are auto-appended via the operator's own SSH; nodes that reject the operator entirely fail the whole cycle with the exact repair command. Preflight report is written to `/tmp/os-update-/ssh-preflight.txt`. Escape hatch: `--skip-ssh-preflight` for bootstrap. +- Drain with a PDB conflict → uncordon and mark the node `SKIPPED_DRAIN_FAILED`, never `--force`. +- Node reboot fails to come back within timeout → hard stop, escalate. Do NOT rebuild the node or touch k3s config. +- kubelet doesn't return `Ready` → hard stop, escalate. Do NOT touch k3s config. +- Post-node cluster health check red → first retry up to `HEALTH_MAX_RETRIES` (default 10) more times at `HEALTH_RETRY_INTERVAL_SECONDS` (default 30s) to let transient reinit windows settle (DEV-595). Short-circuit halt immediately on any `CrashLoopBackOff`/`ImagePullBackOff`/`ErrImagePull`/`CreateContainerConfigError`/`InvalidImageName`. Still red after all attempts → hard stop, do not proceed to the next node. +- Absolute rule: **no k3s config, no manifests, no PVs, no service files touched** — apt/dpkg only. + +## Typical invocations + +```bash +# Dry-run: print the ordered plan, touch nothing. +./os-update.sh --dry-run + +# Full cycle (weekly, triggered by the Paperclip routine). +./os-update.sh + +# Retry a single node (after fixing a manual issue). +./update-node.sh k3s-worker-3 + +# Restart a partial cycle from a specific node onward. +./os-update.sh --start-from k3s-worker-4 + +# Ad-hoc: reconcile Docker on a single node (e.g. after emergency ops). +./ensure-node-docker.sh k3s-worker-3 +``` + +Logs land in `/tmp/os-update-/` on the machine that ran the cycle. diff --git a/infrastructure/scripts/os-update/cluster-health.sh b/infrastructure/scripts/os-update/cluster-health.sh new file mode 100755 index 0000000..017e42e --- /dev/null +++ b/infrastructure/scripts/os-update/cluster-health.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# cluster-health.sh — verify k3s cluster is fully healthy. +# Exits 0 on green, 1 on any failure. Used as preflight and post-node check by os-update.sh. +# +# Runs kubectl commands against whatever the current KUBECONFIG resolves to; +# invoke via `ssh root@$CONTROL_PLANE_HOST bash -s < cluster-health.sh` to +# check the cluster from an operator machine without local kubeconfig. +# +# Environment overrides: +# RETRY_ON_TRANSIENT=1 — one retry after 30s for non-Ready-but-Running pods +# VERBOSE=1 — dump full failing rows on non-zero exit + +set -euo pipefail + +fail=0 + +echo "=== nodes ===" +kubectl get nodes -o wide + +not_ready=$(kubectl get nodes -o json | jq -r ' + .items[] + | select(.status.conditions[] | select(.type=="Ready" and .status!="True")) + | .metadata.name +' || true) +if [ -n "$not_ready" ]; then + echo "FAIL: nodes not Ready: $not_ready" + fail=1 +fi + +echo +echo "=== node pressure conditions ===" +pressure=$(kubectl get nodes -o json | jq -r ' + .items[] + | . as $n + | .status.conditions[] + | select(.type=="DiskPressure" or .type=="MemoryPressure" or .type=="PIDPressure") + | select(.status=="True") + | "\($n.metadata.name) \(.type)=True" +' || true) +if [ -n "$pressure" ]; then + echo "FAIL: node pressure: $pressure" + fail=1 +else + echo " (none)" +fi + +check_bad_pods() { + kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded --no-headers 2>/dev/null || true +} +check_notready_pods() { + kubectl get pods -A -o json | jq -r ' + .items[] + | select(.status.phase=="Running") + | select([.status.conditions[]?|select(.type=="Ready")|.status] | contains(["False"])) + | "\(.metadata.namespace)/\(.metadata.name)" + ' 2>/dev/null || true +} + +echo +echo "=== pods not Running/Succeeded ===" +bad_pods=$(check_bad_pods) +if [ -n "$bad_pods" ]; then + if [ "${RETRY_ON_TRANSIENT:-0}" = "1" ]; then + echo " transient? re-checking in 30s..." + sleep 30 + bad_pods=$(check_bad_pods) + fi +fi +if [ -n "$bad_pods" ]; then + echo "FAIL: pods not Running/Succeeded:" + echo "$bad_pods" + fail=1 +else + echo " (none)" +fi + +echo +echo "=== pods Running but not Ready ===" +notready=$(check_notready_pods) +if [ -n "$notready" ] && [ "${RETRY_ON_TRANSIENT:-0}" = "1" ]; then + echo " transient? re-checking in 30s..." + sleep 30 + notready=$(check_notready_pods) +fi +if [ -n "$notready" ]; then + echo "FAIL: pods Running-but-not-Ready:" + echo "$notready" + fail=1 +else + echo " (none)" +fi + +echo +echo "=== deployments below desired replicas ===" +deploy_bad=$(kubectl get deploy -A -o json | jq -r ' + .items[] + | select((.status.readyReplicas // 0) < (.spec.replicas // 1)) + | "\(.metadata.namespace)/\(.metadata.name) \(.status.readyReplicas // 0)/\(.spec.replicas)" +' || true) +if [ -n "$deploy_bad" ]; then + echo "FAIL: deployments not fully ready:" + echo "$deploy_bad" + fail=1 +else + echo " (none)" +fi + +echo +echo "=== statefulsets below desired replicas ===" +sts_bad=$(kubectl get sts -A -o json | jq -r ' + .items[] + | select((.status.readyReplicas // 0) < (.spec.replicas // 1)) + | "\(.metadata.namespace)/\(.metadata.name) \(.status.readyReplicas // 0)/\(.spec.replicas)" +' || true) +if [ -n "$sts_bad" ]; then + echo "FAIL: statefulsets not fully ready:" + echo "$sts_bad" + fail=1 +else + echo " (none)" +fi + +if [ "$fail" -ne 0 ]; then + echo + echo "CLUSTER-HEALTH: FAIL" + exit 1 +fi + +echo +echo "CLUSTER-HEALTH: OK" diff --git a/infrastructure/scripts/os-update/ensure-node-docker.sh b/infrastructure/scripts/os-update/ensure-node-docker.sh new file mode 100755 index 0000000..cf3c7ab --- /dev/null +++ b/infrastructure/scripts/os-update/ensure-node-docker.sh @@ -0,0 +1,112 @@ +#!/bin/bash +# ensure-node-docker.sh — ensure docker.io is installed, enabled, and running on +# a node that carries (or should carry) the `basicstack.de/docker=true` label. +# +# Runs from the operator machine (or the control plane); needs kubectl and +# ssh access to root@. +# +# Behavior: +# - If the node has label `basicstack.de/docker=true`, ensure docker.io is +# installed, marked `apt-mark manual`, systemd `docker` is enabled+active, +# and /var/run/docker.sock exists. +# - Re-apply the label (idempotent) so that returning nodes always end the +# step in a known state. +# - If the node does NOT carry the label, this is a no-op — do NOT install +# docker on nodes that were not designated to run the Forgejo runner. +# +# Usage: +# ensure-node-docker.sh +# +# Exit codes: +# 0 — node is either not designated (no label) or Docker is confirmed healthy +# 1 — hard failure: label present but docker could not be brought up +# +# Motivated by DEV-499: the weekly rolling OS update was purging docker.io from +# workers, which broke the Forgejo runner (DEV-498). Making Docker part of the +# post-reboot reconciliation removes the manual "apt-get install docker.io && +# systemctl enable --now docker && kubectl label node" step. +set -euo pipefail + +NODE="${1:-}" +if [ -z "$NODE" ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +CONTROL_PLANE_HOST="${CONTROL_PLANE_HOST:-178.105.17.239}" +SSH_OPTS="${SSH_OPTS:--o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new}" +DOCKER_LABEL_KEY="basicstack.de/docker" + +log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] ensure-docker/$NODE: $*"; } +die() { log "FATAL: $*"; exit 1; } + +# node -> ssh target. Keep in sync with update-node.sh::node_ssh_target. +node_ssh_target() { + case "$1" in + k3s-cp-1) echo "root@178.105.17.239" ;; + k3s-worker-1) echo "-J root@$CONTROL_PLANE_HOST root@10.42.1.2" ;; + k3s-worker-2) echo "-J root@$CONTROL_PLANE_HOST root@10.42.1.3" ;; + k3s-worker-3) echo "root@167.233.121.121" ;; + k3s-worker-4) echo "root@128.140.3.80" ;; + k3s-worker-5) echo "root@167.233.192.86" ;; + k3s-update-runner) echo "root@167.233.79.65" ;; + *) die "unknown node $1 — update node_ssh_target() in $0" ;; + esac +} + +# --- 1. is this node designated to run Docker? -------------------------------- +kubectl get node "$NODE" >/dev/null || die "node $NODE not found in cluster" +LABEL_VAL=$(kubectl get node "$NODE" \ + -o jsonpath="{.metadata.labels.${DOCKER_LABEL_KEY//./\\.}}" 2>/dev/null || echo "") + +if [ "$LABEL_VAL" != "true" ]; then + log "no ${DOCKER_LABEL_KEY}=true label — skipping Docker reconciliation" + exit 0 +fi + +SSH_TARGET=$(node_ssh_target "$NODE") +log "label ${DOCKER_LABEL_KEY}=true present — reconciling docker.io via $SSH_TARGET" + +# --- 2. ensure docker.io on the node ----------------------------------------- +REMOTE=$(cat <<'REMOTE' +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +APT_OPTS='-y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' + +# Recover from any half-finished dpkg state before touching apt. +dpkg --configure -a >/dev/null 2>&1 || true + +if ! dpkg -s docker.io >/dev/null 2>&1; then + echo "docker.io not installed — installing" + apt-get update + apt-get $APT_OPTS install docker.io +else + echo "docker.io already installed" +fi + +# Keep docker.io out of the reach of apt-get autoremove --purge, which is what +# the weekly OS-update cycle runs. Idempotent. +apt-mark manual docker.io >/dev/null + +systemctl enable --now docker + +# Wait for the docker socket to appear so the forgejo-runner pod can bind it. +for _ in $(seq 1 30); do + if [ -S /var/run/docker.sock ]; then break; fi + sleep 1 +done +if [ ! -S /var/run/docker.sock ]; then + echo "docker.sock missing after enable" >&2 + systemctl status docker --no-pager | tail -20 >&2 + exit 1 +fi + +echo "docker OK: $(docker version --format '{{.Server.Version}}')" +REMOTE +) + +ssh $SSH_OPTS $SSH_TARGET "bash -s" <<< "$REMOTE" | sed 's/^/ /' + +# --- 3. re-apply the label (idempotent, in case someone stripped it) --------- +kubectl label node "$NODE" "${DOCKER_LABEL_KEY}=true" --overwrite >/dev/null +log "docker + label reconciled OK" diff --git a/infrastructure/scripts/os-update/os-update.sh b/infrastructure/scripts/os-update/os-update.sh new file mode 100755 index 0000000..fbd7915 --- /dev/null +++ b/infrastructure/scripts/os-update/os-update.sh @@ -0,0 +1,461 @@ +#!/bin/bash +# os-update.sh — full weekly rolling OS-update cycle. +# +# Behavior: +# 1. preflight cluster health (fail-closed) +# 2. take an etcd snapshot +# 3. compute node order: +# workers (non-Stalwart first, Stalwart-hosting fsn1 workers last) +# then control-plane nodes (etcd followers first, leader last). +# 3b. SSH-key drift preflight: verify operator can reach every planned node +# and that cp-1's pubkey is present in root's authorized_keys (auto-heal +# by appending if missing; hard-fail if unreachable). See DEV-587. +# 4. call update-node.sh for each worker and update-cp-node.sh for each CP, +# halting on any failure +# 5. finalization: health snapshot + apt history digest +# +# Usage: +# os-update.sh [--dry-run] [--start-from ] [--only ] +# [--skip-cps] [--only-cps] [--skip-ssh-preflight] +# +# Flags: +# --skip-cps run workers only (legacy behavior, useful for narrow patches). +# --only-cps skip workers, only walk the CP phase. +# --only update a single node (worker or CP, routed automatically). +# --skip-ssh-preflight skip the SSH-drift preflight (escape hatch for bootstrap +# scenarios where cp-1's pubkey is intentionally absent). +# +# Environment: +# CONTROL_PLANE_HOST (default 178.105.17.239) +# POST_CP_WAIT_SECONDS (default 180) — settle between CPs after uncordon. +# All env vars honored by update-node.sh / update-cp-node.sh are honored here. +# +# Read OS_UPDATE_PROCEDURE.md alongside this script; the script mirrors it +# step-for-step and the doc is the authoritative reference. CP handling is +# delegated to update-cp-node.sh; see CP_UPDATE_PROCEDURE.md. +set -euo pipefail + +CONTROL_PLANE_HOST="${CONTROL_PLANE_HOST:-178.105.17.239}" +POST_CP_WAIT_SECONDS="${POST_CP_WAIT_SECONDS:-180}" +DRY_RUN=0 +START_FROM="" +ONLY="" +SKIP_CPS=0 +ONLY_CPS=0 +SKIP_SSH_PREFLIGHT="${SKIP_SSH_PREFLIGHT:-0}" + +while [ $# -gt 0 ]; do + case "$1" in + --dry-run) DRY_RUN=1; shift ;; + --start-from) START_FROM="$2"; shift 2 ;; + --only) ONLY="$2"; shift 2 ;; + --skip-cps) SKIP_CPS=1; shift ;; + --only-cps) ONLY_CPS=1; shift ;; + --skip-ssh-preflight) SKIP_SSH_PREFLIGHT=1; shift ;; + -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +STAMP=$(date -u +%Y%m%dT%H%M%SZ) +LOG_DIR="/tmp/os-update-${STAMP}" +mkdir -p "$LOG_DIR" + +log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_DIR/main.log"; } +die() { log "FATAL: $*"; exit 1; } + +# node_ssh_endpoint +# Prints the ssh argv fragment (may include `-J bastion`) used to reach a node. +# Private-IP workers go through cp-1 via ProxyJump; every other node has a +# public IP. Returns non-zero for an unknown node. Callers pass the output +# unquoted so `-J` is treated as its own arg. +node_ssh_endpoint() { + case "$1" in + k3s-cp-1) echo "root@178.105.17.239" ;; + k3s-cp-2) echo "root@188.245.85.199" ;; + k3s-cp-3) echo "root@49.13.92.162" ;; + k3s-worker-1) echo "-J root@$CONTROL_PLANE_HOST root@10.42.1.2" ;; + k3s-worker-2) echo "-J root@$CONTROL_PLANE_HOST root@10.42.1.3" ;; + k3s-worker-3) echo "root@167.233.121.121" ;; + k3s-worker-4) echo "root@128.140.3.80" ;; + k3s-worker-5) echo "root@167.233.192.86" ;; + k3s-worker-6) echo "root@49.12.223.133" ;; + k3s-update-runner) echo "root@167.233.79.65" ;; + *) return 1 ;; + esac +} + +log "=== os-update.sh cycle $STAMP ===" +log "log dir: $LOG_DIR" + +# --- 0. sanity checks --------------------------------------------------------- +command -v kubectl >/dev/null || die "kubectl not on PATH" +command -v jq >/dev/null || die "jq not on PATH (needed for health checks)" + +# --- 1. preflight ------------------------------------------------------------- +log "[preflight] cluster health" +if ! RETRY_ON_TRANSIENT=1 "$SCRIPT_DIR/cluster-health.sh" | tee "$LOG_DIR/preflight.log"; then + die "cluster is not healthy at preflight — refuse to start OS updates" +fi + +# --- 2. etcd snapshot --------------------------------------------------------- +if [ "$DRY_RUN" -eq 0 ]; then + log "[preflight] taking k3s etcd snapshot" + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new root@"$CONTROL_PLANE_HOST" \ + "k3s etcd-snapshot save --name pre-os-update-$(date +%Y%m%d)" | tee "$LOG_DIR/etcd-snapshot.log" || \ + log "WARN: etcd snapshot failed — continuing but this is a risk on CP reboot" +else + log "[preflight] DRY-RUN — skipping etcd snapshot" +fi + +# --- 3. node ordering --------------------------------------------------------- +# Ordering rule: +# - workers first (non-Stalwart, then Stalwart-hosting fsn1 last) +# - control-plane nodes AFTER workers, one at a time, etcd followers first, +# leader last. Rationale (see CP_UPDATE_PROCEDURE.md): +# * two of three CPs down = etcd quorum loss = write outage. Only one CP +# cordoned/rebooting at a time. +# * draining a follower is a no-op for raft leadership; draining the leader +# forces a re-election, so doing followers first minimises leader flapping. +# Since DEV-510 (2026-08-22) the cluster runs HA embedded etcd across +# cp-1/cp-2/cp-3 with 3-node quorum. This script delegates all CP steps to +# update-cp-node.sh, which handles swap (idempotent), preflight, drain, apt, +# reboot with /livez polling, etcd rejoin check, and finalize. +STALWART_NODE=$(kubectl -n stalwart get pod -l app=stalwart -o jsonpath='{.items[*].spec.nodeName}' 2>/dev/null | tr ' ' '\n' | sort -u || true) +# If the pod's not currently up (e.g. Pending) we still want to protect fsn1 workers. + +CP_NODES=$(kubectl get nodes -l node-role.kubernetes.io/control-plane -o jsonpath='{.items[*].metadata.name}' 2>/dev/null | tr ' ' '\n' || true) +ALL_NODES=$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n') + +is_cp() { + local n="$1" + for c in $CP_NODES; do [ "$c" = "$n" ] && return 0; done + return 1 +} + +workers=() +stalwart_workers=() +for n in $ALL_NODES; do + is_cp "$n" && continue + if [ -n "$STALWART_NODE" ] && [ "$n" = "$STALWART_NODE" ]; then + stalwart_workers+=("$n") + continue + fi + # Any fsn1 worker is a potential Stalwart host — schedule after non-Stalwart nodes. + loc=$(kubectl get node "$n" -o jsonpath='{.metadata.labels.csi\.hetzner\.cloud/location}' 2>/dev/null || echo "") + if [ "$loc" = "fsn1" ]; then + stalwart_workers+=("$n") + else + workers+=("$n") + fi +done + +WORKER_ORDER=("${workers[@]}" "${stalwart_workers[@]}") + +# --- 3b. control-plane ordering (leader last) -------------------------------- +# CP-node → public IP map. Kept out-of-cluster so a full-cluster incident +# doesn't hide etcd from the operator machine. +declare -A CP_HOST=( + [k3s-cp-1]="178.105.17.239" + [k3s-cp-2]="188.245.85.199" + [k3s-cp-3]="49.13.92.162" +) + +etcd_leader_ip() { + # Query etcd leader endpoint from the operator's control-plane host. Uses + # the same TLS material as update-cp-node.sh. Prints "IP" on success, + # nothing on failure. + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o BatchMode=yes \ + root@"$CONTROL_PLANE_HOST" \ + 'ETCDCTL_API=3 etcdctl \ + --endpoints=https://127.0.0.1:2379 \ + --cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \ + --cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \ + --key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key \ + endpoint status --cluster -w simple 2>/dev/null' \ + | awk -F, '$5 ~ /true/ {print $1}' \ + | sed -E 's|https?://([^:]+):.*|\1|' +} + +CP_ORDER=() +if [ -n "$CP_NODES" ]; then + LEADER_IP="$(etcd_leader_ip || true)" + leader_node="" + followers=() + for n in $CP_NODES; do + ip="${CP_HOST[$n]:-}" + if [ -n "$ip" ] && [ "$ip" = "$LEADER_IP" ]; then + leader_node="$n" + else + followers+=("$n") + fi + done + if [ -n "$leader_node" ]; then + CP_ORDER=("${followers[@]}" "$leader_node") + log "[plan] etcd leader is $leader_node ($LEADER_IP) — updating followers first, leader last" + else + # Couldn't identify leader — refuse to guess. Fall back to no CPs. + CP_ORDER=() + log "[plan] WARN could not identify etcd leader — skipping CP phase this cycle" + fi +fi + +# Route --only automatically to worker or CP list. +if [ -n "$ONLY" ]; then + if is_cp "$ONLY"; then + CP_ORDER=("$ONLY") + WORKER_ORDER=() + else + WORKER_ORDER=("$ONLY") + CP_ORDER=() + fi +elif [ -n "$START_FROM" ]; then + new=() + skip=1 + for n in "${WORKER_ORDER[@]}"; do + [ "$n" = "$START_FROM" ] && skip=0 + [ $skip -eq 0 ] && new+=("$n") + done + WORKER_ORDER=("${new[@]}") + # If START_FROM is a CP, trim CP_ORDER instead. + if is_cp "$START_FROM"; then + new_cp=() + skip=1 + for n in "${CP_ORDER[@]:-}"; do + [ -z "$n" ] && continue + [ "$n" = "$START_FROM" ] && skip=0 + [ $skip -eq 0 ] && new_cp+=("$n") + done + CP_ORDER=("${new_cp[@]}") + WORKER_ORDER=() + fi +fi + +[ "$SKIP_CPS" -eq 1 ] && CP_ORDER=() +[ "$ONLY_CPS" -eq 1 ] && WORKER_ORDER=() + +log "[plan] workers (${#WORKER_ORDER[@]}): ${WORKER_ORDER[*]:-none}" +log "[plan] control-planes (${#CP_ORDER[@]}): ${CP_ORDER[*]:-none}" +{ + echo "# workers" + printf '%s\n' "${WORKER_ORDER[@]:-}" + echo "# control-planes" + printf '%s\n' "${CP_ORDER[@]:-}" +} > "$LOG_DIR/plan.txt" + +if [ "$DRY_RUN" -eq 1 ]; then + log "DRY-RUN — plan written, no node touched. Exiting." + exit 0 +fi + +# --- 3c. SSH-key drift preflight --------------------------------------------- +# DEV-587 / DEV-586 (2026-09-27) — some k3s nodes drift to lose cp-1's SSH +# pubkey from /root/.ssh/authorized_keys (probable Hetzner cloud-init resync +# from the HC SSH-keys API; the operator key is NOT in that keyset). When this +# happens mid-cycle the whole run aborts partway through drain / peer-probe. +# This preflight proves the operator can SSH to every planned node and that +# cp-1's pubkey is present in root's authorized_keys — auto-heals drift via +# the operator's own SSH access, and hard-fails BEFORE anything is drained if +# a node is genuinely unreachable. +ssh_drift_preflight() { + local ok=() repaired=() problem=() + + log "[preflight] SSH-key drift check for planned nodes" + + local cp1_pubkey cp1_key_body + if ! cp1_pubkey=$(ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \ + root@"$CONTROL_PLANE_HOST" 'cat /root/.ssh/id_rsa.pub' 2>/dev/null); then + die "SSH-drift preflight: cannot read cp-1 pubkey from $CONTROL_PLANE_HOST — fix operator->cp-1 SSH first" + fi + cp1_pubkey=$(printf '%s' "$cp1_pubkey" | tr -d '\r\n') + cp1_key_body=$(printf '%s' "$cp1_pubkey" | awk '{print $2}') + [ -n "$cp1_key_body" ] || die "SSH-drift preflight: could not parse cp-1 pubkey (got: $cp1_pubkey)" + log " cp-1 pubkey OK (${cp1_key_body:0:32}...)" + + local plan_nodes=() + for n in "${WORKER_ORDER[@]:-}" "${CP_ORDER[@]:-}"; do + [ -z "$n" ] && continue + plan_nodes+=("$n") + done + if [ ${#plan_nodes[@]} -eq 0 ]; then + log " (no planned nodes — nothing to preflight)" + return 0 + fi + + for n in "${plan_nodes[@]}"; do + local endpoint + if ! endpoint=$(node_ssh_endpoint "$n"); then + log " $n: unknown ssh endpoint — refusing" + problem+=("$n (no ssh mapping in node_ssh_endpoint)") + continue + fi + + # 1. reachability from the operator machine. + # shellcheck disable=SC2086 # $endpoint may include `-J bastion`; deliberate word-split. + if ! ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \ + $endpoint true 2>/dev/null; then + problem+=("$n ($endpoint - operator SSH refused)") + continue + fi + + # 2. cp-1 is the source of the key; drift check would be pointless on it. + if [ "$n" = "k3s-cp-1" ]; then + ok+=("$n") + continue + fi + + # 3. cp-1 pubkey present? + # shellcheck disable=SC2086 + if ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \ + $endpoint "grep -qF '$cp1_key_body' /root/.ssh/authorized_keys" 2>/dev/null; then + ok+=("$n") + continue + fi + + # 4. auto-heal — append cp-1's pubkey via the (already verified) operator SSH. + log " $n: cp-1 pubkey MISSING from /root/.ssh/authorized_keys — appending" + # shellcheck disable=SC2086 + if ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \ + $endpoint "install -d -m 700 /root/.ssh && printf '%s\n' '$cp1_pubkey' >> /root/.ssh/authorized_keys && chmod 600 /root/.ssh/authorized_keys" \ + 2>/dev/null; then + # shellcheck disable=SC2086 + if ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \ + $endpoint "grep -qF '$cp1_key_body' /root/.ssh/authorized_keys" 2>/dev/null; then + repaired+=("$n") + else + problem+=("$n (auto-heal wrote but grep-verify failed)") + fi + else + problem+=("$n ($endpoint - auto-heal write refused)") + fi + done + + log "[preflight] SSH-drift summary:" + log " ok (${#ok[@]}): ${ok[*]:-none}" + log " repaired (${#repaired[@]}): ${repaired[*]:-none}" + log " problem (${#problem[@]}): ${problem[*]:-none}" + + { + echo "# SSH-drift preflight — $(date -u +%FT%TZ)" + echo "# cp-1 pubkey: $cp1_pubkey" + printf 'ok\t%s\n' "${ok[*]:-}" + printf 'repaired\t%s\n' "${repaired[*]:-}" + printf 'problem\t%s\n' "${problem[*]:-}" + } > "$LOG_DIR/ssh-preflight.txt" + + if [ ${#problem[@]} -gt 0 ]; then + log "SSH-drift preflight FAILED for: ${problem[*]}" + log " cp-1 pubkey to install:" + log " $cp1_pubkey" + log " From any machine that CAN reach the failing node, run:" + log " ssh root@ \"grep -qF '$cp1_key_body' /root/.ssh/authorized_keys || echo '$cp1_pubkey' >> /root/.ssh/authorized_keys\"" + die "SSH-drift preflight failed — refuse to start rolling update" + fi +} + +if [ "$SKIP_SSH_PREFLIGHT" -eq 1 ]; then + log "[preflight] SSH-drift check SKIPPED (--skip-ssh-preflight)" +else + ssh_drift_preflight +fi + +# --- 4. worker loop ---------------------------------------------------------- +updated=() +skipped=() +for n in "${WORKER_ORDER[@]}"; do + [ -z "$n" ] && continue + log "====================================================================" + log "==> updating worker $n" + log "====================================================================" + NODE_LOG="$LOG_DIR/${n}.log" + set +e + "$SCRIPT_DIR/update-node.sh" "$n" 2>&1 | tee "$NODE_LOG" + rc=${PIPESTATUS[0]} + set -e + case $rc in + 0) updated+=("$n") ;; + 3) skipped+=("$n:drain-blocked") ;; + *) die "update-node.sh failed for $n (rc=$rc). Cycle halted. See $NODE_LOG" ;; + esac +done + +# --- 4b. control-plane loop -------------------------------------------------- +# One CP at a time, followers first, leader last. Delegates to update-cp-node.sh +# --run (Phase A add-swap is idempotent, Phase B..F run with ASSUME_YES=1). +# Between CPs we run cluster-health.sh and re-verify etcd cluster status; halt +# on any yellow signal. +if [ ${#CP_ORDER[@]} -gt 0 ]; then + log "====================================================================" + log "==> control-plane phase (${#CP_ORDER[@]} nodes): ${CP_ORDER[*]}" + log "====================================================================" + first_cp=1 + for cp in "${CP_ORDER[@]}"; do + [ -z "$cp" ] && continue + if [ $first_cp -eq 0 ]; then + log "-- health gate between CPs (settle ${POST_CP_WAIT_SECONDS}s, then re-check)" + sleep "$POST_CP_WAIT_SECONDS" + if ! RETRY_ON_TRANSIENT=1 "$SCRIPT_DIR/cluster-health.sh" 2>&1 | tee "$LOG_DIR/inter-cp-health-before-${cp}.log"; then + die "cluster health degraded between CPs — halting before $cp. See $LOG_DIR/" + fi + # Re-check etcd cluster status; every member must still be started. + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new root@"$CONTROL_PLANE_HOST" \ + 'ETCDCTL_API=3 etcdctl \ + --endpoints=https://127.0.0.1:2379 \ + --cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \ + --cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \ + --key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key \ + endpoint status --cluster -w table' 2>&1 | tee -a "$LOG_DIR/inter-cp-health-before-${cp}.log" || \ + die "etcd endpoint status failed between CPs — halting before $cp" + fi + log "====================================================================" + log "==> updating control-plane $cp" + log "====================================================================" + CP_LOG="$LOG_DIR/${cp}.log" + set +e + ASSUME_YES=1 "$SCRIPT_DIR/update-cp-node.sh" "$cp" --run 2>&1 | tee "$CP_LOG" + rc=${PIPESTATUS[0]} + set -e + case $rc in + 0) updated+=("$cp") ;; + *) die "update-cp-node.sh failed for $cp (rc=$rc). Cycle halted. See $CP_LOG" ;; + esac + first_cp=0 + done +fi + +# --- 5. finalization ---------------------------------------------------------- +log "====================================================================" +log "==> finalization" +log "====================================================================" + +log "[final] cluster health" +"$SCRIPT_DIR/cluster-health.sh" | tee "$LOG_DIR/final-health.log" || \ + die "final cluster health failed after cycle. Do NOT declare success." + +log "[final] apt history digest" +{ + for n in "${updated[@]}"; do + echo "=== $n ===" + if ! t=$(node_ssh_endpoint "$n"); then + echo " (unknown ssh target)" + continue + fi + # shellcheck disable=SC2086 # $t may include `-J bastion`; deliberate word-split. + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new $t \ + 'zgrep -h "Commandline\|Install\|Upgrade\|Remove" /var/log/apt/history.log* 2>/dev/null | tail -40' 2>/dev/null \ + || echo " (could not read apt history)" + done +} | tee "$LOG_DIR/apt-history.log" + +log "=== summary ===" +log "updated (${#updated[@]}): ${updated[*]:-none}" +log "skipped (${#skipped[@]}): ${skipped[*]:-none}" +log "logs: $LOG_DIR" + +if [ ${#skipped[@]} -gt 0 ]; then + log "cycle finished with skipped nodes — return code 4 so the caller can escalate" + exit 4 +fi + +log "cycle complete." diff --git a/infrastructure/scripts/os-update/update-cp-1.sh b/infrastructure/scripts/os-update/update-cp-1.sh new file mode 100755 index 0000000..d9aaf88 --- /dev/null +++ b/infrastructure/scripts/os-update/update-cp-1.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# update-cp-1.sh -- SUPERSEDED thin wrapper. +# +# Since DEV-515 (2026-08-23) the HA-aware entry point is update-cp-node.sh. +# This wrapper forwards to `update-cp-node.sh k3s-cp-1 "$@"` so historical +# callers keep working (routines, runbook references, board-approval docs). +# +# See ../CP_UPDATE_PROCEDURE.md for the current procedure. +# The old cp-1-only design lives in git history at commit 19af691. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +exec "$SCRIPT_DIR/update-cp-node.sh" k3s-cp-1 "$@" diff --git a/infrastructure/scripts/os-update/update-cp-node.sh b/infrastructure/scripts/os-update/update-cp-node.sh new file mode 100755 index 0000000..f161048 --- /dev/null +++ b/infrastructure/scripts/os-update/update-cp-node.sh @@ -0,0 +1,604 @@ +#!/bin/bash +# update-cp-node.sh — controlled OS update for any HA k3s control-plane node. +# +# See ../CP_UPDATE_PROCEDURE.md for the full design rationale. +# +# Usage: +# update-cp-node.sh --dry-run # print what would be done, touch nothing +# update-cp-node.sh --add-swap # Phase A only (idempotent, safe standalone) +# update-cp-node.sh --preflight # Phase B only +# update-cp-node.sh --drain # Phase C only (cordon + drain) +# update-cp-node.sh --apt # Phase D only (requires already fully drained) +# update-cp-node.sh --reboot # Phase E only (requires --apt reported REBOOT_REQUIRED=yes) +# update-cp-node.sh --finalize # Phase F only (uncordon + verify) +# update-cp-node.sh --run # all phases with confirmation between each (or ASSUME_YES=1) +# +# must be one of: k3s-cp-1, k3s-cp-2, k3s-cp-3. +# +# Environment overrides: +# SWAP_SIZE_MB default 4096 (>=2048 required) +# SWAP_PATH default /swapfile +# DRAIN_TIMEOUT_SECONDS default 600 +# REBOOT_MAX_WAIT_SECONDS default 600 +# POST_UNCORDON_WAIT_SECONDS default 180 +# MIN_TARGET_MEM_MIB default 200 (target-CP MemAvailable floor mid-drain) +# MAX_KUBECTL_SECONDS default 5 (kine-latency guardrail; softened for HA etcd) +# SSH_OPTS default "-o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" +# ASSUME_YES=1 skip interactive confirmations in --run +# DRY_RUN=1 do not execute state-mutating commands; log-only +# +# Log directory contract (matches update-node.sh): +# Every command's stdout+stderr is tee'd to /tmp/os-update--.log on +# the operator machine. Attach it to the execution ticket at the end. +# +# NEVER touches k3s config, k3s services, containerd, or any manifest. Only +# fixes it will attempt: apt/dpkg recovery on the target CP (Phase D). Any other +# problem -> escalate and STOP. + +set -euo pipefail + +# --------------------------------------------------------------------------- # +# CP topology — public/routable IPs used to reach each CP via ssh + /livez. +# Kept out-of-cluster on purpose: during a full-cluster incident the operator +# machine must be able to reach each CP without going through k3s. +# --------------------------------------------------------------------------- # +declare -A CP_HOST=( + [k3s-cp-1]="178.105.17.239" + [k3s-cp-2]="188.245.85.199" + [k3s-cp-3]="49.13.92.162" +) + +# --------------------------------------------------------------------------- # +# Arg parse — expect as $1 +# --------------------------------------------------------------------------- # +usage() { grep -E '^# ' "$0" | sed 's/^# \{0,1\}//'; exit 2; } +[ $# -ge 2 ] || usage + +NODE="$1"; shift +TARGET_HOST="${CP_HOST[$NODE]:-}" +if [ -z "$TARGET_HOST" ]; then + echo "unknown CP node: $NODE (allowed: ${!CP_HOST[*]})" >&2 + exit 2 +fi + +# --------------------------------------------------------------------------- # +# Config +# --------------------------------------------------------------------------- # +SWAP_SIZE_MB="${SWAP_SIZE_MB:-4096}" +SWAP_PATH="${SWAP_PATH:-/swapfile}" +DRAIN_TIMEOUT_SECONDS="${DRAIN_TIMEOUT_SECONDS:-600}" +REBOOT_MAX_WAIT_SECONDS="${REBOOT_MAX_WAIT_SECONDS:-600}" +POST_UNCORDON_WAIT_SECONDS="${POST_UNCORDON_WAIT_SECONDS:-180}" +MIN_TARGET_MEM_MIB="${MIN_TARGET_MEM_MIB:-200}" +MAX_KUBECTL_SECONDS="${MAX_KUBECTL_SECONDS:-5}" +SSH_OPTS="${SSH_OPTS:--o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new}" +DRY_RUN="${DRY_RUN:-0}" +ASSUME_YES="${ASSUME_YES:-0}" + +TS="$(date -u +%Y%m%dT%H%M%SZ)" +LOG_LOCAL="/tmp/os-update-${NODE}-${TS}.log" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +HEALTH_SCRIPT="$SCRIPT_DIR/cluster-health.sh" + +# Pick a peer CP for etcd status probes. Requirements: +# - not the target +# - etcdctl available (via non-login ssh PATH) +# We probe each candidate at parse time. Order preference: cp-1 first (it has +# etcdctl installed since the pre-HA era), then cp-2, then cp-3. The Phase A +# `--add-swap` step also installs `etcd-client` via apt so cp-2/cp-3 pick up +# etcdctl on their first pass. +peer_host_for() { + local target="$1" + local order=(k3s-cp-1 k3s-cp-2 k3s-cp-3) + for n in "${order[@]}"; do + [ "$n" = "$target" ] && continue + local h="${CP_HOST[$n]}" + if ssh $SSH_OPTS -o BatchMode=yes "root@$h" 'command -v etcdctl >/dev/null 2>&1' 2>/dev/null; then + echo "$h"; return 0 + fi + done + # In DRY_RUN we don't need a real etcdctl-capable peer. + if [ "${DRY_RUN:-0}" = "1" ]; then + for n in "${order[@]}"; do + [ "$n" != "$target" ] && { echo "${CP_HOST[$n]}"; return 0; } + done + fi + # No non-target CP has etcdctl. Return empty so Phase B/E can fail + # explicitly with a targeted "install etcd-client on cp-X first" message. + echo "" + return 0 +} +PEER_HOST="$(peer_host_for "$NODE")" + +# --------------------------------------------------------------------------- # +# Logging + safe-run helpers +# --------------------------------------------------------------------------- # +log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG_LOCAL"; } +die() { log "FATAL: $*"; exit 1; } +warn() { log "WARN: $*"; } + +run() { + if [ "$DRY_RUN" = "1" ]; then + log "DRY-RUN would exec: $*" + return 0 + fi + log "exec: $*" + "$@" 2>&1 | tee -a "$LOG_LOCAL" +} + +ssh_run() { + local target="${1:-root@$TARGET_HOST}"; shift || true + if [ "$DRY_RUN" = "1" ]; then + log "DRY-RUN would ssh $target: $*" + return 0 + fi + log "ssh $target: $*" + ssh $SSH_OPTS "$target" "$@" 2>&1 | tee -a "$LOG_LOCAL" +} + +# ssh_run_stdin: pipe a heredoc through bash -s on the target; used for multi-line remote blocks. +ssh_run_stdin() { + local target="root@${TARGET_HOST}" + if [ "$DRY_RUN" = "1" ]; then + log "DRY-RUN would ssh $target with stdin script:" + sed 's/^/ | /' | tee -a "$LOG_LOCAL" + return 0 + fi + log "ssh $target (heredoc)" + ssh $SSH_OPTS "$target" "bash -s" 2>&1 | tee -a "$LOG_LOCAL" +} + +confirm() { + local prompt="$1" + if [ "$ASSUME_YES" = "1" ]; then + log "confirm SKIPPED (ASSUME_YES=1): $prompt" + return 0 + fi + echo -n " >>> $prompt Continue? [y/N] " + read -r a + case "$a" in y|Y|yes|YES) return 0 ;; *) die "aborted by operator" ;; esac +} + +# --------------------------------------------------------------------------- # +# etcd helpers — run etcdctl on the target OR on a peer CP. +# --------------------------------------------------------------------------- # +ETCDCTL_ENV='ETCDCTL_API=3 etcdctl \ + --endpoints=https://127.0.0.1:2379 \ + --cacert=/var/lib/rancher/k3s/server/tls/etcd/server-ca.crt \ + --cert=/var/lib/rancher/k3s/server/tls/etcd/server-client.crt \ + --key=/var/lib/rancher/k3s/server/tls/etcd/server-client.key' + +etcd_status_via() { + # $1 = host (IP) + local host="$1" + ssh $SSH_OPTS "root@$host" "$ETCDCTL_ENV endpoint status --cluster -w table" 2>&1 +} + +etcd_leader_name() { + # returns the etcd member NAME (e.g. k3s-cp-3-9d305472) whose row shows IS LEADER=true. + # Uses simple-format output (no -w table) for reliable parsing. + local host="$1" + ssh $SSH_OPTS "root@$host" "$ETCDCTL_ENV endpoint status --cluster -w simple" 2>/dev/null \ + | awk -F, '$5 ~ /true/ {print $1}' +} + +target_is_leader() { + # returns 0 if the target IP is the current etcd leader, 1 otherwise. + local host="$1" # peer to query from + local target="$2" # target IP to compare + local leader_ep + leader_ep=$(ssh $SSH_OPTS "root@$host" "$ETCDCTL_ENV endpoint status --cluster -w simple" 2>/dev/null \ + | awk -F, '$5 ~ /true/ {print $1}') + # leader_ep looks like https://49.13.92.162:2379 + echo "$leader_ep" | grep -q "://${target}:" && return 0 || return 1 +} + +# --------------------------------------------------------------------------- # +# Phase A — Add swap on the target CP (idempotent) +# --------------------------------------------------------------------------- # +phase_add_swap() { + log "=== Phase A: add swap on $NODE ($SWAP_SIZE_MB MiB at $SWAP_PATH) ===" + [ "$SWAP_SIZE_MB" -ge 2048 ] || die "SWAP_SIZE_MB=$SWAP_SIZE_MB below 2048 MiB guardrail" + + cat </dev/null | grep -qx "\$SWAP_PATH"; then + echo "swap already on at \$SWAP_PATH -- skipping" + free -h + exit 0 +fi + +# Root filesystem free space check -- abort if less than 2*swap free. +avail_mb=\$(df -m --output=avail / | tail -1 | tr -d ' ') +need_mb=\$(( SIZE_MB * 2 )) +if [ "\$avail_mb" -lt "\$need_mb" ]; then + echo "ERROR: only \${avail_mb} MiB free on /, need \${need_mb} MiB (2x swap for safety)" + exit 1 +fi + +# Create swapfile. fallocate is fast; dd is the fallback. +if ! fallocate -l "\${SIZE_MB}M" "\$SWAP_PATH" 2>/dev/null; then + dd if=/dev/zero of="\$SWAP_PATH" bs=1M count="\$SIZE_MB" status=progress +fi +chmod 600 "\$SWAP_PATH" +mkswap "\$SWAP_PATH" +swapon "\$SWAP_PATH" + +# Persist via fstab (dedup). +if ! grep -q "^\$SWAP_PATH " /etc/fstab; then + echo "\$SWAP_PATH none swap sw 0 0" >> /etc/fstab +fi + +# Moderate swappiness -- swap as safety net, not aggressive paging. +sysctl -w vm.swappiness=10 +if [ ! -f /etc/sysctl.d/99-k3s-swap.conf ] || ! grep -q '^vm.swappiness' /etc/sysctl.d/99-k3s-swap.conf; then + echo 'vm.swappiness=10' > /etc/sysctl.d/99-k3s-swap.conf +fi + +echo "--- swap after ---" +free -h +swapon --show +sysctl vm.swappiness + +# Ensure etcdctl is available for etcd-quorum probes (idempotent apt install). +# Needed because when this CP is the target of a later update, another CP +# must probe etcd cluster status; if cp-1 is the target, one of cp-2/cp-3 +# is the probing peer and must have etcdctl. +if ! command -v etcdctl >/dev/null 2>&1; then + echo "--- installing etcd-client (provides etcdctl) ---" + export DEBIAN_FRONTEND=noninteractive + apt-get update -y >/dev/null + apt-get install -y etcd-client + command -v etcdctl && etcdctl version +fi +REMOTE + + if [ "$DRY_RUN" != "1" ]; then + log "verifying kubelet still Ready after swap add" + local ready + ready=$(kubectl get node "$NODE" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo Unknown) + [ "$ready" = "True" ] || die "kubelet on $NODE not Ready after swap add -- halt" + log "kubelet Ready=True -- Phase A complete" + fi +} + +# --------------------------------------------------------------------------- # +# Guardrail probes +# --------------------------------------------------------------------------- # +kubectl_latency_s() { + local start end + start=$(date +%s) + kubectl get nodes >/dev/null 2>&1 || echo "kubectl-error" >&2 + end=$(date +%s) + echo $(( end - start )) +} + +target_mem_available_mib() { + ssh $SSH_OPTS "root@$TARGET_HOST" "awk '/^MemAvailable:/{printf \"%d\n\", \$2/1024}' /proc/meminfo" 2>/dev/null || echo 0 +} + +guard_kine_healthy() { + local s + s=$(kubectl_latency_s) + if [ "$s" -gt "$MAX_KUBECTL_SECONDS" ]; then + die "kubectl get nodes took ${s}s (>${MAX_KUBECTL_SECONDS}s) -- etcd slow, HALT" + fi + log " kubectl-latency ok: ${s}s" +} + +guard_target_memory() { + local m + m=$(target_mem_available_mib) + if [ "$m" -lt "$MIN_TARGET_MEM_MIB" ]; then + die "$NODE MemAvailable=${m} MiB below ${MIN_TARGET_MEM_MIB} MiB floor -- HALT" + fi + log " $NODE mem ok: MemAvailable=${m} MiB" +} + +# --------------------------------------------------------------------------- # +# Phase B — Preflight +# --------------------------------------------------------------------------- # +phase_preflight() { + log "=== Phase B: preflight for $NODE ===" + + log "-- cluster health" + if [ "$DRY_RUN" != "1" ]; then + if ! RETRY_ON_TRANSIENT=1 "$HEALTH_SCRIPT" 2>&1 | tee -a "$LOG_LOCAL"; then + die "cluster is not healthy -- refuse to start $NODE update" + fi + else + log "DRY-RUN would run: $HEALTH_SCRIPT" + fi + + log "-- confirm $NODE is a control-plane node" + if [ "$DRY_RUN" != "1" ]; then + local is_cp + is_cp=$(kubectl get node "$NODE" -o jsonpath='{.metadata.labels.node-role\.kubernetes\.io/control-plane}' 2>/dev/null || echo "") + [ "$is_cp" = "true" ] || die "$NODE is not labelled control-plane -- refuse (use update-node.sh for workers)" + fi + + log "-- verify no OTHER CP is currently cordoned" + if [ "$DRY_RUN" != "1" ]; then + local other_cordoned + other_cordoned=$(kubectl get nodes -l node-role.kubernetes.io/control-plane=true \ + -o json | jq -r --arg n "$NODE" '.items[] | select(.metadata.name != $n) | select(.spec.unschedulable == true) | .metadata.name' \ + | tr '\n' ' ') + if [ -n "${other_cordoned// /}" ]; then + die "another CP is already cordoned: $other_cordoned -- refuse (one CP at a time)" + fi + log " no other CP cordoned -- proceeding" + fi + + log "-- etcd cluster status (all members must be started)" + if [ "$DRY_RUN" != "1" ]; then + if [ -z "$PEER_HOST" ]; then + die "no non-target CP has etcdctl installed -- run \`update-cp-node.sh --add-swap\` first on one of the OTHER CPs (that step installs etcd-client), then retry" + fi + etcd_status_via "$PEER_HOST" | tee -a "$LOG_LOCAL" + fi + + log "-- swap on $NODE" + if [ "$DRY_RUN" != "1" ]; then + local swap_total + swap_total=$(ssh $SSH_OPTS "root@$TARGET_HOST" "awk '/^SwapTotal:/{print \$2}' /proc/meminfo") + [ "${swap_total:-0}" -ge $((2 * 1024 * 1024)) ] \ + || die "$NODE SwapTotal=${swap_total} KiB below 2 GiB -- run --add-swap first" + log " $NODE SwapTotal=$(( swap_total / 1024 )) MiB" + fi + + log "-- kubectl-latency probe" + if [ "$DRY_RUN" != "1" ]; then guard_kine_healthy; fi + + log "-- record current etcd leader" + if [ "$DRY_RUN" != "1" ] && [ -n "$PEER_HOST" ]; then + if target_is_leader "$PEER_HOST" "$TARGET_HOST"; then + warn "$NODE IS the current etcd leader. Per CP ordering rule, prefer updating a follower first." + warn " Not aborting -- operator/agent must confirm this is intentional." + else + log " $NODE is a FOLLOWER -- safe to proceed." + fi + fi + + log "-- k3s etcd snapshot" + ssh_run "root@$TARGET_HOST" "k3s etcd-snapshot save --name pre-cp-os-update-${NODE}-${TS}" + ssh_run "root@$TARGET_HOST" "ls -la /var/lib/rancher/k3s/server/db/snapshots/ | tail -10" + + log "=== Phase B: preflight OK ===" +} + +# --------------------------------------------------------------------------- # +# Phase C — Cordon + drain +# --------------------------------------------------------------------------- # +phase_drain() { + log "=== Phase C: cordon + drain $NODE ===" + + log "-- cordon $NODE" + run kubectl cordon "$NODE" + + log "-- drain $NODE (timeout ${DRAIN_TIMEOUT_SECONDS}s)" + set +e + if [ "$DRY_RUN" = "1" ]; then + log "DRY-RUN would run: kubectl drain $NODE --ignore-daemonsets --delete-emptydir-data --timeout=${DRAIN_TIMEOUT_SECONDS}s" + local rc=0 + else + kubectl drain "$NODE" \ + --ignore-daemonsets \ + --delete-emptydir-data \ + --timeout="${DRAIN_TIMEOUT_SECONDS}s" 2>&1 | tee -a "$LOG_LOCAL" + local rc=${PIPESTATUS[0]} + fi + set -e + + if [ "$rc" -ne 0 ]; then + log "drain FAILED (rc=$rc). Never force. Uncordoning." + run kubectl uncordon "$NODE" + die "drain failed on $NODE -- investigate PDB / orphan pods; do NOT proceed" + fi + + log "-- post-drain guardrails" + if [ "$DRY_RUN" != "1" ]; then + guard_kine_healthy + guard_target_memory + fi + + log "=== Phase C: $NODE drained ===" +} + +# --------------------------------------------------------------------------- # +# Phase D — apt on the target +# --------------------------------------------------------------------------- # +phase_apt() { + log "=== Phase D: apt on $NODE ===" + + cat <<'REMOTE' | ssh_run_stdin +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +APT_OPTS='-y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' + +uname -r > /root/pre-apt-kernel +dpkg-query -W -f='${Package}\t${Version}\n' > /root/pre-apt-packages.tsv +echo "pre-apt kernel: $(cat /root/pre-apt-kernel)" + +if dpkg --audit | grep -qE .; then + echo "dpkg audit reported issues, running dpkg --configure -a" + dpkg --configure -a || true +fi + +apt-get update + +if ! apt-get $APT_OPTS upgrade; then + echo "upgrade failed, attempting apt-get -f install" + apt-get $APT_OPTS -f install + apt-get $APT_OPTS upgrade +fi + +apt-get $APT_OPTS dist-upgrade +apt-get $APT_OPTS autoremove --purge +apt-get clean + +if [ -f /var/run/reboot-required ]; then + echo "REBOOT_REQUIRED=yes" + echo "REBOOT_REASON</dev/null || echo "(no package list)" + echo "EOF" +else + echo "REBOOT_REQUIRED=no" +fi +REMOTE + + log "=== Phase D: apt complete (check REBOOT_REQUIRED in the log) ===" +} + +# --------------------------------------------------------------------------- # +# Phase E — Reboot and wait for target /livez + kubelet Ready + etcd rejoin +# --------------------------------------------------------------------------- # +phase_reboot() { + log "=== Phase E: reboot $NODE ===" + + if [ "$DRY_RUN" != "1" ]; then + log "issuing 'systemctl reboot' on $NODE (ssh will drop; expected)" + ssh $SSH_OPTS "root@$TARGET_HOST" 'systemctl reboot' 2>&1 | tee -a "$LOG_LOCAL" || true + log "waiting 15s for ssh to fully drop before polling" + sleep 15 + else + log "DRY-RUN would ssh root@$TARGET_HOST 'systemctl reboot'" + fi + + log "-- poll $NODE api-server /livez (timeout ${REBOOT_MAX_WAIT_SECONDS}s)" + if [ "$DRY_RUN" != "1" ]; then + local deadline=$(( $(date +%s) + REBOOT_MAX_WAIT_SECONDS )) + local code=000 + # /livez is auth-gated on this cluster (returns 401 without a bearer token). + # Any HTTP response from the api-server (200/401/403) means it is up and + # serving; only network-level failures (000, connection refused) count as + # "not yet back". curl --max-time keeps the poll cycle snappy after reboot. + while [ $(date +%s) -lt $deadline ]; do + code=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 5 "https://$TARGET_HOST:6443/livez" 2>/dev/null || echo 000) + case "$code" in + 200|401|403) + log " $NODE api-server responding (/livez HTTP $code)" + break + ;; + esac + sleep 5 + done + case "$code" in + 200|401|403) : ;; + *) die "$NODE api-server did not return within ${REBOOT_MAX_WAIT_SECONDS}s (last code=$code) -- escalate; check 'hcloud server describe $NODE' and Hetzner console" ;; + esac + fi + + log "-- verify etcd cluster status from peer ($PEER_HOST) -- $NODE should be 'started'" + if [ "$DRY_RUN" != "1" ] && [ -n "$PEER_HOST" ]; then + etcd_status_via "$PEER_HOST" | tee -a "$LOG_LOCAL" + fi + + log "-- wait for kubelet Ready on $NODE (max 300s)" + if [ "$DRY_RUN" != "1" ]; then + local deadline=$(( $(date +%s) + 300 )) + local ready=Unknown + while [ $(date +%s) -lt $deadline ]; do + ready=$(kubectl get node "$NODE" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo Unknown) + [ "$ready" = "True" ] && break + sleep 5 + done + [ "$ready" = "True" ] || die "kubelet on $NODE never returned Ready -- escalate (do NOT change k3s config)" + log " kubelet Ready=True" + fi + + log "=== Phase E: $NODE is back ===" +} + +# --------------------------------------------------------------------------- # +# Phase F — Uncordon + verify + finalize +# --------------------------------------------------------------------------- # +phase_finalize() { + log "=== Phase F: uncordon + verify $NODE ===" + + run kubectl uncordon "$NODE" + + log "-- settle wait ${POST_UNCORDON_WAIT_SECONDS}s" + [ "$DRY_RUN" = "1" ] || sleep "$POST_UNCORDON_WAIT_SECONDS" + + log "-- cluster health" + if [ "$DRY_RUN" != "1" ]; then + if ! RETRY_ON_TRANSIENT=1 "$HEALTH_SCRIPT" 2>&1 | tee -a "$LOG_LOCAL"; then + die "cluster health failed after $NODE update -- escalate, do NOT touch k3s" + fi + fi + + log "-- etcd cluster status (all three should be started)" + if [ "$DRY_RUN" != "1" ] && [ -n "$PEER_HOST" ]; then + etcd_status_via "$PEER_HOST" | tee -a "$LOG_LOCAL" + fi + + log "-- apt history summary (audit)" + ssh_run "root@$TARGET_HOST" 'zgrep -h "Commandline\|Install\|Upgrade\|Remove" /var/log/apt/history.log* 2>/dev/null | tail -60' + + log "-- old snapshots (>30d) -- listing only, review manually" + ssh_run "root@$TARGET_HOST" 'find /var/lib/rancher/k3s/server/db/snapshots/ -type f -mtime +30 -name "pre-*" -print 2>/dev/null || true' + + log "=== $NODE OS update complete -- attach $LOG_LOCAL to the execution ticket ===" +} + +# --------------------------------------------------------------------------- # +# --run — orchestrate all phases with confirmations +# --------------------------------------------------------------------------- # +phase_run_all() { + log "=== full $NODE update run (log: $LOG_LOCAL) ===" + + confirm "Phase A (add swap) -- proceed?" + phase_add_swap + + confirm "Phase B (preflight) -- proceed?" + phase_preflight + + confirm "Phase C (cordon + drain) -- proceed?" + phase_drain + + confirm "Phase D (apt) -- proceed?" + phase_apt + + confirm "Phase E (reboot $NODE; api-server on THIS node unavailable ~90-180s, other 2 CPs keep serving) -- proceed?" + phase_reboot + + confirm "Phase F (uncordon + verify) -- proceed?" + phase_finalize + + log "=== FULL RUN COMPLETE for $NODE ===" +} + +# --------------------------------------------------------------------------- # +# Phase dispatch +# --------------------------------------------------------------------------- # +: > "$LOG_LOCAL" +log "update-cp-node.sh started (NODE=$NODE, TARGET_HOST=$TARGET_HOST, PEER_HOST=$PEER_HOST, DRY_RUN=$DRY_RUN)" +log "log file: $LOG_LOCAL" + +case "$1" in + --dry-run) + DRY_RUN=1 + export DRY_RUN + log "DRY_RUN=1 -- walking Phases A..F without touching state" + phase_add_swap + phase_preflight + phase_drain + phase_apt + phase_reboot + phase_finalize + ;; + --add-swap) phase_add_swap ;; + --preflight) phase_preflight ;; + --drain) phase_drain ;; + --apt) phase_apt ;; + --reboot) phase_reboot ;; + --finalize) phase_finalize ;; + --run) phase_run_all ;; + -h|--help) usage ;; + *) echo "unknown arg: $1" >&2; usage ;; +esac diff --git a/infrastructure/scripts/os-update/update-node.sh b/infrastructure/scripts/os-update/update-node.sh new file mode 100755 index 0000000..af7743a --- /dev/null +++ b/infrastructure/scripts/os-update/update-node.sh @@ -0,0 +1,234 @@ +#!/bin/bash +# update-node.sh — drain, apt-update, reboot-if-needed, wait, uncordon a single node. +# +# Usage: +# update-node.sh +# +# Runs from an operator machine (or from the control plane); needs kubectl and +# ssh access to root@. Node → SSH target resolution is in +# `node_ssh_target` below; edit that mapping when you add nodes. +# +# NEVER touches k3s config, k3s services, containerd, or any manifest. +# Only fixes it will attempt: apt/dpkg recovery on the same node (see step 3). +# +# Environment overrides: +# DRAIN_TIMEOUT_SECONDS (default 600) +# REBOOT_MAX_WAIT_SECONDS (default 600) +# POST_UNCORDON_WAIT_SECONDS (default 180) +# HEALTH_MAX_RETRIES (default 10) — extra post-uncordon health retries +# HEALTH_RETRY_INTERVAL_SECONDS (default 30) — delay between those retries +# CONTROL_PLANE_HOST (default 178.105.17.239) +# SSH_OPTS (default "-o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new") +# ASSUME_YES=1 to skip interactive confirmations +set -euo pipefail + +NODE="${1:-}" +if [ -z "$NODE" ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +DRAIN_TIMEOUT_SECONDS="${DRAIN_TIMEOUT_SECONDS:-600}" +REBOOT_MAX_WAIT_SECONDS="${REBOOT_MAX_WAIT_SECONDS:-600}" +POST_UNCORDON_WAIT_SECONDS="${POST_UNCORDON_WAIT_SECONDS:-180}" +HEALTH_MAX_RETRIES="${HEALTH_MAX_RETRIES:-10}" +HEALTH_RETRY_INTERVAL_SECONDS="${HEALTH_RETRY_INTERVAL_SECONDS:-30}" +CONTROL_PLANE_HOST="${CONTROL_PLANE_HOST:-178.105.17.239}" +SSH_OPTS="${SSH_OPTS:--o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new}" + +# Pod-level signatures that do not recover on their own — short-circuit the +# post-uncordon retry loop and halt immediately (DEV-595). +PERSISTENT_FAILURE_PATTERN='CrashLoopBackOff|ImagePullBackOff|ErrImagePull|CreateContainerConfigError|InvalidImageName' + +log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; } +die() { log "FATAL: $*"; exit 1; } + +# node -> ssh target. Private-IP workers are reached via ProxyJump through the CP. +node_ssh_target() { + case "$1" in + k3s-cp-1) echo "root@178.105.17.239" ;; + k3s-worker-1) echo "-J root@$CONTROL_PLANE_HOST root@10.42.1.2" ;; + k3s-worker-2) echo "-J root@$CONTROL_PLANE_HOST root@10.42.1.3" ;; + k3s-worker-3) echo "root@167.233.121.121" ;; + k3s-worker-4) echo "root@128.140.3.80" ;; + k3s-worker-5) echo "root@167.233.192.86" ;; + k3s-worker-6) echo "root@49.12.223.133" ;; + k3s-update-runner) echo "root@167.233.79.65" ;; + *) die "unknown node $1 — update node_ssh_target() in $0" ;; + esac +} + +SSH_TARGET=$(node_ssh_target "$NODE") + +log "=== update-node.sh $NODE ===" +log "ssh target: $SSH_TARGET" + +# --- 1. pre-check -------------------------------------------------------------- +log "[1/7] pre-check" +kubectl get node "$NODE" >/dev/null || die "node $NODE not found in cluster" +READY=$(kubectl get node "$NODE" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}') +[ "$READY" = "True" ] || die "node $NODE is not Ready before we start (Ready=$READY)" + +for cond in DiskPressure MemoryPressure PIDPressure; do + v=$(kubectl get node "$NODE" -o jsonpath="{.status.conditions[?(@.type==\"$cond\")].status}") + [ "$v" = "True" ] && die "node $NODE has $cond=True — refuse to update" +done + +# --- 2. cordon + drain -------------------------------------------------------- +log "[2/7] cordon + drain (timeout ${DRAIN_TIMEOUT_SECONDS}s)" +kubectl cordon "$NODE" + +set +e +kubectl drain "$NODE" \ + --ignore-daemonsets \ + --delete-emptydir-data \ + --timeout="${DRAIN_TIMEOUT_SECONDS}s" +DRAIN_RC=$? +set -e + +if [ $DRAIN_RC -ne 0 ]; then + log "drain FAILED (rc=$DRAIN_RC). Never force. Uncordoning $NODE and marking SKIPPED." + kubectl uncordon "$NODE" || true + echo "SKIPPED_DRAIN_FAILED $NODE" + exit 3 +fi + +# --- 3. apt update on the node ------------------------------------------------ +log "[3/7] apt update/upgrade on $NODE" +REMOTE_APT=$(cat <<'REMOTE' +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +APT_OPTS='-y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold' + +# Recover from any half-finished dpkg state before touching apt. +if ! dpkg --audit | grep -qE .; then + : +else + echo "dpkg audit reported issues, running dpkg --configure -a" + dpkg --configure -a || true +fi + +apt-get update + +# Before any upgrade/autoremove pass, pin docker.io as manually-installed on +# nodes where it is present. Without this, apt autoremove --purge has, in the +# past, taken docker.io out from under the Forgejo runner (DEV-498/DEV-499). +if dpkg -s docker.io >/dev/null 2>&1; then + apt-mark manual docker.io >/dev/null +fi + +# Try upgrade; on broken deps, one attempt at apt-get -f install then retry. +if ! apt-get $APT_OPTS upgrade; then + echo "upgrade failed, attempting apt-get -f install" + apt-get $APT_OPTS -f install + apt-get $APT_OPTS upgrade +fi + +apt-get $APT_OPTS dist-upgrade +apt-get $APT_OPTS autoremove --purge +apt-get clean + +if [ -f /var/run/reboot-required ]; then + echo "REBOOT_REQUIRED=yes" + echo "REBOOT_REASON</dev/null || echo "(no package list)" + echo "EOF" +else + echo "REBOOT_REQUIRED=no" +fi +REMOTE +) + +APT_OUT=$(ssh $SSH_OPTS $SSH_TARGET "bash -s" <<< "$REMOTE_APT") +echo "$APT_OUT" | sed 's/^/ /' + +if echo "$APT_OUT" | grep -q '^REBOOT_REQUIRED=yes'; then + REBOOT=1 +else + REBOOT=0 +fi + +# --- 4. reboot if required ---------------------------------------------------- +if [ "$REBOOT" -eq 1 ]; then + log "[4/7] reboot required — rebooting $NODE" + ssh $SSH_OPTS $SSH_TARGET 'systemctl reboot' || true + + # Give SSH a moment to actually drop before we start polling. + sleep 15 + + deadline=$(( $(date +%s) + REBOOT_MAX_WAIT_SECONDS )) + while [ $(date +%s) -lt $deadline ]; do + if ssh $SSH_OPTS -o ConnectTimeout=5 $SSH_TARGET 'uptime' >/dev/null 2>&1; then + log " $NODE ssh is back" + break + fi + sleep 10 + done + + if ! ssh $SSH_OPTS -o ConnectTimeout=5 $SSH_TARGET 'uptime' >/dev/null 2>&1; then + die "node $NODE did not return within ${REBOOT_MAX_WAIT_SECONDS}s — escalate" + fi +else + log "[4/7] no reboot needed" +fi + +# --- 5. wait for kubelet Ready ------------------------------------------------ +log "[5/7] wait for kubelet Ready on $NODE" +deadline=$(( $(date +%s) + 300 )) +while [ $(date +%s) -lt $deadline ]; do + READY=$(kubectl get node "$NODE" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo Unknown) + [ "$READY" = "True" ] && break + sleep 5 +done +[ "$READY" = "True" ] || die "kubelet on $NODE never returned Ready — escalate (do NOT change k3s config)" +log " Ready=True" + +# --- 5b. ensure Docker on designated nodes ------------------------------------ +# Do this BEFORE uncordoning so the runner pod's first scheduling attempt +# succeeds instead of racing through ContainerCreating. Idempotent no-op on +# nodes that are not labeled basicstack.de/docker=true. (DEV-499) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +log "[5b/7] ensure docker.io on nodes labeled basicstack.de/docker=true" +"$SCRIPT_DIR/ensure-node-docker.sh" "$NODE" + +# --- 6. uncordon -------------------------------------------------------------- +log "[6/7] uncordon $NODE" +kubectl uncordon "$NODE" + +# --- 7. post-node settle ------------------------------------------------------ +# First check at POST_UNCORDON_WAIT_SECONDS, then up to HEALTH_MAX_RETRIES more +# attempts at HEALTH_RETRY_INTERVAL_SECONDS. Transient yellow signals (Init:*, +# Running-but-not-Ready, deployment-below-replicas during reschedule) often +# clear within a minute or two; previously they tripped a FATAL on the first +# check. A pod in CrashLoopBackOff (or image-pull failure) is persistent and +# short-circuits the loop immediately. (DEV-595) +log "[7/7] post-node settle (${POST_UNCORDON_WAIT_SECONDS}s) + health check" +sleep "$POST_UNCORDON_WAIT_SECONDS" + +attempt=1 +max_attempts=$((HEALTH_MAX_RETRIES + 1)) +while :; do + set +e + HEALTH_OUT=$(RETRY_ON_TRANSIENT=0 "$SCRIPT_DIR/cluster-health.sh" 2>&1) + HEALTH_RC=$? + set -e + + echo "$HEALTH_OUT" + + if [ "$HEALTH_RC" -eq 0 ]; then + log "=== $NODE update: OK (health attempt $attempt/$max_attempts) ===" + break + fi + + if echo "$HEALTH_OUT" | grep -qE "$PERSISTENT_FAILURE_PATTERN"; then + die "cluster health failed after updating $NODE with persistent pod error (CrashLoopBackOff/ImagePullBackOff/etc.) on attempt $attempt — STOP the cycle, do NOT continue" + fi + + if [ "$attempt" -ge "$max_attempts" ]; then + die "cluster health still failing after $attempt attempts (interval ${HEALTH_RETRY_INTERVAL_SECONDS}s) following update of $NODE — STOP the cycle, do NOT continue" + fi + + log " cluster health yellow on attempt $attempt/$max_attempts (no persistent signatures) — retrying in ${HEALTH_RETRY_INTERVAL_SECONDS}s" + sleep "$HEALTH_RETRY_INTERVAL_SECONDS" + attempt=$((attempt + 1)) +done diff --git a/infrastructure/scripts/provision-k3s-worker.sh b/infrastructure/scripts/provision-k3s-worker.sh index b7479db..2cda77e 100755 --- a/infrastructure/scripts/provision-k3s-worker.sh +++ b/infrastructure/scripts/provision-k3s-worker.sh @@ -82,7 +82,8 @@ echo "This may take a few minutes..." ssh root@${NEW_NODE_IP} "curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION='v${CLUSTER_VERSION}' sh -s - agent \ --server https://${CONTROL_PLANE_IP}:6443 \ - --token '${K3S_TOKEN}'" + --token '${K3S_TOKEN}' \ + --node-ip ${NEW_NODE_IP}" if [ $? -ne 0 ]; then echo -e "${RED}Error: k3s installation failed${NC}" @@ -111,3 +112,4 @@ echo "Next steps:" echo " 1. Label the node if needed: kubectl label nodes node-role.kubernetes.io/worker=worker" echo " 2. Monitor pod scheduling: kubectl get pods --all-namespaces -o wide | grep " echo " 3. Verify flannel annotation: kubectl get node -o yaml | grep flannel" +echo " 4. Verify INTERNAL-IP is the private IP (not public): kubectl get node -o wide" diff --git a/velero/prometheusrule.yaml b/velero/prometheusrule.yaml new file mode 100644 index 0000000..dfea3f6 --- /dev/null +++ b/velero/prometheusrule.yaml @@ -0,0 +1,108 @@ +--- +# Velero alerting for cluster-reconstruction backups (DEV-569 / DEV-574). +# +# Metrics come from the Velero server on :8085/metrics, scraped via +# velero/servicemonitor.yaml. +# +# All our Velero Schedules are daily (see velero/schedules/*), so the +# freshness alert uses a 26h window (24h cadence + 2h grace). +# +# Notes: +# - `velero_backup_last_successful_timestamp` is emitted only after a +# schedule has had at least one successful run. Freshly created +# schedules produce no series until their first success, so the +# "missed" alert doesn't fire on day 0 — it fires once a schedule +# has succeeded at least once and then goes >26h without another. +# - `schedule=""` is Velero's rollup for one-off (non-Schedule) +# backups; we filter it out so ad-hoc backups don't page. +# - Velero v1.18.1 does not expose `velero_backup_repository_available` +# (mentioned in the DEV-574 spec), so the repo-health alert here +# watches `velero_backup_location_status_gauge` instead — that +# covers the same operational failure mode ("backups can't land +# because the target storage is unreachable"). +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: velero-alerts + namespace: velero + labels: + app.kubernetes.io/name: velero + app.kubernetes.io/part-of: velero + release: kube-prometheus-stack +spec: + groups: + - name: velero.backups + interval: 60s + rules: + - alert: VeleroBackupFailed + expr: sum by (schedule) (increase(velero_backup_failure_total{schedule!=""}[30m])) > 0 + for: 0m + labels: + team: infra + severity: warning + service: velero + component: backup + annotations: + summary: "Velero backup failed ({{ $labels.schedule }})" + description: | + Velero recorded {{ $value | printf "%.0f" }} new backup + failure(s) for schedule `{{ $labels.schedule }}` in the + last 30 minutes. + Investigate: `velero backup get -n velero` and + `velero backup describe --details`. + - alert: VeleroBackupPartial + expr: sum by (schedule) (increase(velero_backup_partial_failure_total{schedule!=""}[30m])) > 0 + for: 0m + labels: + team: infra + severity: warning + service: velero + component: backup + annotations: + summary: "Velero backup completed with partial failures ({{ $labels.schedule }})" + description: | + Velero recorded {{ $value | printf "%.0f" }} partial + failure(s) for schedule `{{ $labels.schedule }}` in the + last 30 minutes. Some items were skipped or errored even + though the backup finished. + Investigate: `velero backup describe --details`. + - alert: VeleroBackupMissed + # All schedules are daily → alert if the last success is + # older than 26h (24h cadence + 2h grace). + expr: (time() - velero_backup_last_successful_timestamp{schedule!=""}) > (26 * 3600) + for: 15m + labels: + team: infra + severity: warning + service: velero + component: backup + annotations: + summary: "Velero schedule missed daily window ({{ $labels.schedule }})" + description: | + Schedule `{{ $labels.schedule }}` has not produced a + successful backup in {{ $value | humanizeDuration }} + (>26h). Expected cadence: daily. + Check: `kubectl -n velero get schedule {{ $labels.schedule }}` + and Velero server logs. + - name: velero.storage + interval: 60s + rules: + - alert: VeleroStorageLocationUnavailable + # Substitutes for the spec's VeleroRepoUnhealthy — see file + # header. `velero_backup_location_status_gauge` is 1 when the + # BackupStorageLocation is Available, 0 when Unavailable. + expr: velero_backup_location_status_gauge == 0 + for: 15m + labels: + team: infra + severity: warning + service: velero + component: backup + annotations: + summary: "Velero BackupStorageLocation unavailable ({{ $labels.backup_location_name }})" + description: | + BackupStorageLocation `{{ $labels.backup_location_name }}` + has reported Unavailable for >15m. New backups will fail + until it is restored. + Check: `kubectl -n velero get backupstoragelocation` and + `velero backup-location get`. diff --git a/velero/restore-drill/00-serviceaccount-rbac.yaml b/velero/restore-drill/00-serviceaccount-rbac.yaml new file mode 100644 index 0000000..7a61adb --- /dev/null +++ b/velero/restore-drill/00-serviceaccount-rbac.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: restore-drill + namespace: velero + labels: + app.kubernetes.io/part-of: velero-restore-drill +--- +# Ops permissions: create/list/get/delete velero Backups+Restores, create+delete +# restore-test-* namespaces. Namespace verbs cannot be name-scoped in RBAC, so we +# accept cluster-wide namespace create/delete. Drill script enforces the +# 'restore-test-' name prefix before deleting. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: velero-restore-drill-ops + labels: + app.kubernetes.io/part-of: velero-restore-drill +rules: + - apiGroups: ["velero.io"] + resources: ["backups", "restores"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["get", "list", "create", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: velero-restore-drill-ops + labels: + app.kubernetes.io/part-of: velero-restore-drill +subjects: + - kind: ServiceAccount + name: restore-drill + namespace: velero +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: velero-restore-drill-ops +--- +# Read-only diff permissions: cluster-scoped read of the workload resources we +# compare between the source and restored namespaces. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: velero-restore-drill-view + labels: + app.kubernetes.io/part-of: velero-restore-drill +subjects: + - kind: ServiceAccount + name: restore-drill + namespace: velero +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view diff --git a/velero/restore-drill/10-drill-script-configmap.yaml b/velero/restore-drill/10-drill-script-configmap.yaml new file mode 100644 index 0000000..03d7362 --- /dev/null +++ b/velero/restore-drill/10-drill-script-configmap.yaml @@ -0,0 +1,224 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: restore-drill-script + namespace: velero + labels: + app.kubernetes.io/part-of: velero-restore-drill +data: + drill.sh: | + #!/bin/bash + # Monthly Velero restore drill for openwebui (DEV-575). + # Picks the latest openwebui-manifests backup (or ${DRILL_BACKUP_OVERRIDE}), + # restores it into restore-test-, diffs counts vs source, cleans up. + # Writes result to ConfigMap velero-restore-drill-last-result and job stdout. + # Exits non-zero on any failure (surfaces via kube_job_status_failed alert). + set -uo pipefail + + SRC_NS="${DRILL_SOURCE_NS:-openwebui}" + SCHEDULE_NAME="${DRILL_SCHEDULE_NAME:-openwebui-manifests}" + RESULT_CM="${DRILL_RESULT_CM:-velero-restore-drill-last-result}" + RESULT_NS="${DRILL_RESULT_NS:-velero}" + RESTORE_TIMEOUT="${DRILL_RESTORE_TIMEOUT:-1800}" + NS_DELETE_TIMEOUT="${DRILL_NS_DELETE_TIMEOUT:-600}" + + TS="$(date -u +%Y%m%d-%H%M%S)" + TGT_NS="restore-test-${TS}" + RESTORE_NAME="drill-${TS}" + + LOG="" + log() { local line; line="[$(date -u +%FT%TZ)] $*"; echo "$line"; LOG+="${line}"$'\n'; } + + DRILL_STATUS="fail" + BACKUP_NAME="" + BACKUP_START="" + BACKUP_COMPLETION="" + RESTORE_PHASE="" + RESTORE_DURATION_S="" + DIFF_SUMMARY="" + + write_result_cm() { + local report="$1" + kubectl -n "${RESULT_NS}" create configmap "${RESULT_CM}" \ + --from-literal=result="${report}" \ + --from-literal=log="${LOG}" \ + --dry-run=client -o yaml \ + | kubectl -n "${RESULT_NS}" apply -f - >/dev/null 2>&1 \ + || log "WARN: failed to write result ConfigMap" + } + + post_webhook() { + local status="$1" backup="$2" restore="$3" restore_phase="$4" \ + restore_duration_s="$5" target_ns="$6" source_ns="$7" diff_summary="$8" + if [[ -z "${PAPERCLIP_WEBHOOK_URL:-}" || -z "${PAPERCLIP_WEBHOOK_SECRET:-}" ]]; then + log "webhook: no PAPERCLIP_WEBHOOK_URL/SECRET, skipping" + return + fi + local body_file=/tmp/webhook.json + printf '{"source":"velero-restore-drill","status":"%s","timestamp":"%s","backup":"%s","restore":"%s","restore_phase":"%s","restore_duration_s":"%s","target_namespace":"%s","source_namespace":"%s","diff_summary":"%s"}\n' \ + "${status}" "${TS}" "${backup}" "${restore}" "${restore_phase}" \ + "${restore_duration_s}" "${target_ns}" "${source_ns}" "${diff_summary}" > "${body_file}" + # DEV-590: trigger is in github_hmac signingMode. Compute HMAC-SHA256 over + # the raw body and send X-Hub-Signature-256; sending Authorization: Bearer + # here is a trap because the home Paperclip's global actor middleware + # rejects any Bearer header that is not a board API key or agent JWT + # before the per-trigger handler runs. + local sig + sig=$(openssl dgst -sha256 -hmac "${PAPERCLIP_WEBHOOK_SECRET}" -r "${body_file}" | awk '{print $1}') + local hc + hc=$(curl -sS -o /tmp/webhook.out -w '%{http_code}' -m 30 \ + -X POST -H "X-Hub-Signature-256: sha256=${sig}" \ + -H "Content-Type: application/json" \ + --data-binary "@${body_file}" \ + "${PAPERCLIP_WEBHOOK_URL}" 2>&1) || hc="000" + log "webhook: HTTP ${hc}" + } + + finalize() { + local end + end="$(date -u +%FT%TZ)" + + if [[ "${TGT_NS}" == restore-test-* ]] && kubectl get ns "${TGT_NS}" >/dev/null 2>&1; then + log "cleanup: deleting namespace ${TGT_NS}" + if kubectl delete ns "${TGT_NS}" --timeout="${NS_DELETE_TIMEOUT}s" >/tmp/nsdelete.log 2>&1; then + log "cleanup: namespace ${TGT_NS} deleted" + else + log "cleanup: WARN failed to fully delete ${TGT_NS}, may be stuck terminating" + fi + fi + + if [[ -n "${RESTORE_NAME}" ]] && kubectl -n velero get restore.velero.io "${RESTORE_NAME}" >/dev/null 2>&1; then + kubectl -n velero delete restore.velero.io "${RESTORE_NAME}" --wait=false >/dev/null 2>&1 || true + fi + + local report + report=$(printf 'drill=%s\ntimestamp=%s\nbackup=%s\nbackup_started=%s\nbackup_completed=%s\nrestore=%s\nrestore_phase=%s\nrestore_duration_s=%s\ntarget_namespace=%s\nsource_namespace=%s\ndiff_summary=%s\nfinished_at=%s\n' \ + "${DRILL_STATUS}" "${TS}" "${BACKUP_NAME}" "${BACKUP_START}" \ + "${BACKUP_COMPLETION}" "${RESTORE_NAME}" "${RESTORE_PHASE}" \ + "${RESTORE_DURATION_S}" "${TGT_NS}" "${SRC_NS}" "${DIFF_SUMMARY}" "${end}") + + log "----- FINAL REPORT -----" + printf '%s\n' "${report}" + + write_result_cm "${report}" + post_webhook "${DRILL_STATUS}" "${BACKUP_NAME}" "${RESTORE_NAME}" \ + "${RESTORE_PHASE}" "${RESTORE_DURATION_S}" "${TGT_NS}" "${SRC_NS}" \ + "${DIFF_SUMMARY}" + + if [[ "${DRILL_STATUS}" == "pass" ]]; then + exit 0 + else + exit 1 + fi + } + trap finalize EXIT + + log "drill start ts=${TS} src_ns=${SRC_NS} schedule=${SCHEDULE_NAME} target=${TGT_NS}" + + if [[ -n "${DRILL_BACKUP_OVERRIDE:-}" ]]; then + BACKUP_NAME="${DRILL_BACKUP_OVERRIDE}" + log "using override backup name: ${BACKUP_NAME}" + else + BACKUP_NAME=$(kubectl -n velero get backups.velero.io \ + -l "velero.io/schedule-name=${SCHEDULE_NAME}" \ + -o go-template='{{range .items}}{{if eq .status.phase "Completed"}}{{.status.completionTimestamp}}|{{.metadata.name}}{{"\n"}}{{end}}{{end}}' 2>/dev/null \ + | sort -r | head -n1 | cut -d'|' -f2) + if [[ -z "${BACKUP_NAME}" ]]; then + log "ERROR: no Completed backup found for schedule=${SCHEDULE_NAME}" + exit 1 + fi + log "latest Completed backup: ${BACKUP_NAME}" + fi + + BACKUP_START=$(kubectl -n velero get backup.velero.io "${BACKUP_NAME}" -o jsonpath='{.status.startTimestamp}' 2>/dev/null || true) + BACKUP_COMPLETION=$(kubectl -n velero get backup.velero.io "${BACKUP_NAME}" -o jsonpath='{.status.completionTimestamp}' 2>/dev/null || true) + + log "creating restore ${RESTORE_NAME} from backup ${BACKUP_NAME} -> ns ${TGT_NS}" + RESTORE_YAML=/tmp/restore.yaml + { + printf 'apiVersion: velero.io/v1\n' + printf 'kind: Restore\n' + printf 'metadata:\n' + printf ' name: %s\n' "${RESTORE_NAME}" + printf ' namespace: velero\n' + printf ' labels:\n' + printf ' app.kubernetes.io/part-of: velero-restore-drill\n' + printf 'spec:\n' + printf ' backupName: %s\n' "${BACKUP_NAME}" + printf ' includedNamespaces:\n' + printf ' - %s\n' "${SRC_NS}" + printf ' namespaceMapping:\n' + printf ' %s: %s\n' "${SRC_NS}" "${TGT_NS}" + printf ' includeClusterResources: false\n' + printf ' restorePVs: false\n' + printf ' existingResourcePolicy: none\n' + printf ' hooks: {}\n' + } > "${RESTORE_YAML}" + if ! kubectl apply -f "${RESTORE_YAML}" >/tmp/apply.log 2>&1; then + log "ERROR: failed to create Restore CR" + cat /tmp/apply.log | while IFS= read -r l; do log " $l"; done + exit 1 + fi + + log "waiting up to ${RESTORE_TIMEOUT}s for restore to finish" + start_epoch=$(date +%s) + while :; do + RESTORE_PHASE=$(kubectl -n velero get restore.velero.io "${RESTORE_NAME}" -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + case "${RESTORE_PHASE}" in + Completed|PartiallyFailed|Failed|FailedValidation) + break + ;; + esac + now=$(date +%s) + if (( now - start_epoch > RESTORE_TIMEOUT )); then + log "ERROR: restore did not reach terminal phase within ${RESTORE_TIMEOUT}s (last=${RESTORE_PHASE})" + exit 1 + fi + sleep 10 + done + RESTORE_DURATION_S=$(( $(date +%s) - start_epoch )) + log "restore phase=${RESTORE_PHASE} duration=${RESTORE_DURATION_S}s" + + if [[ "${RESTORE_PHASE}" != "Completed" && "${RESTORE_PHASE}" != "PartiallyFailed" ]]; then + log "ERROR: restore did not complete (phase=${RESTORE_PHASE})" + kubectl -n velero describe restore.velero.io "${RESTORE_NAME}" 2>&1 | tail -40 | while IFS= read -r l; do log " $l"; done + exit 1 + fi + + if ! kubectl get ns "${TGT_NS}" >/dev/null 2>&1; then + log "ERROR: restore reported ${RESTORE_PHASE} but namespace ${TGT_NS} does not exist" + exit 1 + fi + + count_kind() { + local ns="$1" kind="$2" + kubectl -n "${ns}" get "${kind}" --no-headers 2>/dev/null | wc -l | tr -d ' ' + } + + KINDS=(deployments statefulsets daemonsets replicasets services configmaps secrets serviceaccounts persistentvolumeclaims cronjobs jobs) + DIFF_SUMMARY="" + for k in "${KINDS[@]}"; do + sc=$(count_kind "${SRC_NS}" "${k}") + tc=$(count_kind "${TGT_NS}" "${k}") + DIFF_SUMMARY+="${k}=${sc}/${tc};" + done + log "diff (src/tgt): ${DIFF_SUMMARY}" + + STRICT_KINDS=(deployments statefulsets services configmaps persistentvolumeclaims) + strict_ok=1 + for k in "${STRICT_KINDS[@]}"; do + sc=$(count_kind "${SRC_NS}" "${k}") + tc=$(count_kind "${TGT_NS}" "${k}") + if [[ "${sc}" != "${tc}" ]]; then + log "STRICT MISMATCH kind=${k} src=${sc} tgt=${tc}" + strict_ok=0 + fi + done + + if (( strict_ok == 1 )); then + DRILL_STATUS="pass" + log "drill result: PASS" + else + log "drill result: FAIL (strict kind mismatch)" + exit 1 + fi diff --git a/velero/restore-drill/20-restore-drill-cronjob.yaml b/velero/restore-drill/20-restore-drill-cronjob.yaml new file mode 100644 index 0000000..70f9cce --- /dev/null +++ b/velero/restore-drill/20-restore-drill-cronjob.yaml @@ -0,0 +1,108 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: velero-restore-drill + namespace: velero + labels: + app.kubernetes.io/part-of: velero-restore-drill +spec: + # Monthly at 04:00 UTC on the 1st (per DEV-575 acceptance). + schedule: "0 4 1 * *" + timeZone: "UTC" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + startingDeadlineSeconds: 3600 + jobTemplate: + metadata: + labels: + app.kubernetes.io/part-of: velero-restore-drill + spec: + backoffLimit: 0 + activeDeadlineSeconds: 3600 + ttlSecondsAfterFinished: 604800 # keep completed jobs for 7d for debug + template: + metadata: + labels: + app.kubernetes.io/part-of: velero-restore-drill + spec: + restartPolicy: Never + serviceAccountName: restore-drill + # DEV-579: public DNS for paperclip.home.imicros.de returns the LAN IP + # 10.0.0.2 (split-brain), which is unroutable from the cluster. The + # home reverse proxy at home.imicros.de (46.225.155.185) already + # serves the paperclip.home.imicros.de vhost publicly, so overriding + # the hostname to that public IP lets the webhook reach the routine. + # If the home ISP rotates the public IP, refresh this entry from + # `dig +short home.imicros.de` (DDNS record). + hostAliases: + - ip: "46.225.155.185" + hostnames: + - "paperclip.home.imicros.de" + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + containers: + - name: drill + image: alpine/k8s:1.30.5 + imagePullPolicy: IfNotPresent + command: ["/bin/bash", "/scripts/drill.sh"] + env: + - name: DRILL_SOURCE_NS + value: "openwebui" + - name: DRILL_SCHEDULE_NAME + value: "openwebui-manifests" + - name: DRILL_RESULT_NS + value: "velero" + - name: DRILL_RESULT_CM + value: "velero-restore-drill-last-result" + - name: DRILL_RESTORE_TIMEOUT + value: "1800" + - name: DRILL_NS_DELETE_TIMEOUT + value: "600" + # Optional Paperclip webhook (mounted from SealedSecret if wired). + - name: PAPERCLIP_WEBHOOK_URL + valueFrom: + secretKeyRef: + name: velero-restore-drill-webhook + key: webhookUrl + optional: true + - name: PAPERCLIP_WEBHOOK_SECRET + valueFrom: + secretKeyRef: + name: velero-restore-drill-webhook + key: webhookSecret + optional: true + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + volumeMounts: + - name: script + mountPath: /scripts + readOnly: true + - name: tmp + mountPath: /tmp + - name: kube + mountPath: /.kube + volumes: + - name: script + configMap: + name: restore-drill-script + defaultMode: 0555 + - name: tmp + emptyDir: {} + - name: kube + emptyDir: {} diff --git a/velero/restore-drill/30-webhook-sealed.yaml b/velero/restore-drill/30-webhook-sealed.yaml new file mode 100644 index 0000000..fb25770 --- /dev/null +++ b/velero/restore-drill/30-webhook-sealed.yaml @@ -0,0 +1,17 @@ +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: velero-restore-drill-webhook + namespace: velero +spec: + encryptedData: + webhookSecret: AgB4J8oWfPjZDar6zOp9ezv7M5DrtQFCvN6JgJmT3f6LJBngssanDW0N2nFg76a8L/Pht2RZK8aQq0GyGVbYjxtiQ0lagZGR/7j2yoKgkUyRie99YL7SMBPZ2SM9yBDRzsctHKeLZtmhsqyrmKLFnVs49Au3v2GtpknwOePr/BDZgdTGhaR/NftFuWMak6yPeoXNkGpYK7spJwoWUDN1LyQY/ZoEcgz2iQJ7odKqVE7hmt21an1xv7a/nmIDS8uNWWfYlPspTTxfQvTX4Ti28e5Z7S9kj+fqAVX6zH7tZ/jKODE72+uOya2FnjZ/SjEJ7Lz0+4eZTMWo0Kk0rK+WFY5fQvwOFlOZiCQsL3S2oxIiPjy11ZJh8gBFCXUR1KJNsPNveifsXvOC+kwWA/YaYQRHUArKZ50QOXhkwbEoS7XB1LklJil0IRzScvDSNSwtrpxg80rWVTVZEC5XJVpnRaNOSvfQI7NBYPA/rYRU3OcT0cT7vkBUIK4kgFV/4uIIoalaKcAGtPoHTEciA0avkCOgDSzlE18YZ///1hdtgJWx1OQ2ewpdawmsK1XJPjSreuf8zqxiVQosxI3Q0pTjv47aznjnxV2EDuvhj/gXYfrvO9B47chi2PdesAZt2IeOEOphl94EkVANHAuNgwnsymbK8mdFyybnCcIrWpgaIRMZYy3cQv+tqRwHJrk1cqPnul7fWKu4V7t6SfVY/ML3vsGQOevE4dce/rDquMuAbnWYqmV8YoD3jxPwe8bkhKUcrZM= + webhookUrl: AgBVLLkdlaxiqWyQQXcLj5uewljVQhBQpJw1OG9IMNw+MjQoBss6gqZhHgK2xAddsbnzOEAr/BJ6kJgR8DXldWsdQ9MnQZDc7VLOXs+h399KrIYeDTbuRHwWzp2BaCBSwOxmnd7S6v6h10Qq9rC02F8uRQ5gQ0B/JOVb1kwh+h6whCL3cSC511Wp6dnON2u0QJb3NoIVqGJd24ZcFgOs8w53fSgQgakvbMPYe34/RoHtT/SZmoaIUUzzAC+C1FzRKaJPKVDoPoYy5IhnIMNkecPmyg1hjJzmRQR64grIhO02WBm+lbjXnRqcauAyNeE5NNglYKaQmSr3Ya8zaB1pxm2L5DiUgCyR4O4nlIPH/37zvqa35fVnELmuwYprBCVmCxjulkPlqrh3tmRcR6EEPdd2ECND4E6XUenof57W/VsPg+iiRn/vcqAM3e0NidHhf5GtSxW5VDBmorrzqyWsEZbnNxIA01rNX1pupVTC1YssTdGF1k5/ccWUlwCpZJMeMyR0fHfn6kCsFHbF8lZ6w3VILObiKJnDU7Kkn3RXZ1k9plN21ZipffoSSXDLStQitJhX3a6ADhRazRw/xZjdqgO5iYNfSXih6GI1/6LgbE5UTZA7nqvAX9OqnH1B/7qMGCcwt18l+lF26uStN0OiDOaPdsgdzSpdv0BPibSvc4RdtvVEHiarV11geaMdxS8wZnS+C7GO2Nvx+qcGS6v9G9czIEvvrogcuBd8S3F7z0oiLmNNEeVW/CJwknO+5OkJm/wmFhQTZbRozgte8v9pjsaPZ6k/CzUdAilb+aEdkZ8DesXh5zYlGTMedclc + template: + metadata: + creationTimestamp: null + name: velero-restore-drill-webhook + namespace: velero + type: Opaque +--- diff --git a/velero/restore-drill/README.md b/velero/restore-drill/README.md new file mode 100644 index 0000000..024dec7 --- /dev/null +++ b/velero/restore-drill/README.md @@ -0,0 +1,81 @@ +# Velero restore drill + +Monthly Kubernetes CronJob that exercises the Velero backup chain by restoring +the `openwebui` namespace into a transient `restore-test-` namespace, +diffing resource counts against the live namespace, and cleaning up. + +Wired by [DEV-575](https://forgejo.basicstack.de/basicstack/stack.basicstack.de/wiki/DEV-575) +under the [DEV-569](https://forgejo.basicstack.de/basicstack/stack.basicstack.de/wiki/DEV-569) +Velero cluster-reconstruction backup layer. + +## What it does + +1. Picks the latest `Completed` backup for schedule `openwebui-manifests` + (env `DRILL_BACKUP_OVERRIDE` short-circuits this — used by the failure + drill). +2. Creates a `Restore` CR with namespace mapping + `openwebui: restore-test-`. +3. Waits for the `Restore` to reach a terminal phase (`Completed`, + `PartiallyFailed`, `Failed`, `FailedValidation`) or times out at 30 min. +4. Counts core kinds (deployments, statefulsets, services, configmaps, PVCs, …) + in source vs target and requires strict equality on the manifest-carrying + kinds to pass. +5. Deletes the `restore-test-` namespace and the `Restore` CR. +6. Writes the final report to ConfigMap `velero/velero-restore-drill-last-result` + and logs it to stdout. +7. Optionally POSTs a JSON payload to the Paperclip routine webhook if + the `velero-restore-drill-webhook` Secret is present. The CronJob uses a + `hostAliases` entry to route `paperclip.home.imicros.de` at the home + public IP so the webhook reaches the routine from Hetzner (DEV-579). + Authentication is `github_hmac` — the script computes an HMAC-SHA256 + of the raw body with the webhook secret as key and sends it as + `X-Hub-Signature-256: sha256=`. The matching trigger `signingMode` + is `github_hmac` on routine `c9d941fa-fb97-4e6a-a305-8e46f296e0cd`. + Previously it was `bearer`, but the home Paperclip's global actor + middleware rejects any `Authorization: Bearer …` whose value is not a + board API key or an agent JWT before the per-trigger handler runs, + making the bearer path unreachable (DEV-590). + +## Failure signalling + +The Job exits non-zero on any failure. The alerting stack (DEV-574) will fire +`KubeJobFailed` on the `velero-restore-drill-*` job name and route the alert +through existing Alertmanager receivers. Ops can inspect the last result via: + +``` +kubectl -n velero get cm velero-restore-drill-last-result -o jsonpath='{.data.result}' +kubectl -n velero get cm velero-restore-drill-last-result -o jsonpath='{.data.log}' +``` + +## Manual runs + +Kick a drill on demand: + +``` +kubectl -n velero create job --from=cronjob/velero-restore-drill drill-adhoc-$(date -u +%Y%m%d-%H%M%S) +``` + +Force a failure to test the alert wiring (uses a bogus backup name): + +``` +kubectl -n velero create job drill-adhoc-fail-$(date -u +%s) \ + --image=alpine/k8s:1.30.5 \ + --dry-run=client -o yaml \ + | kubectl -n velero apply -f - # only if you need a scratch, prefer: +kubectl -n velero create job --from=cronjob/velero-restore-drill drill-fail-$(date -u +%Y%m%d-%H%M%S) --dry-run=client -o yaml \ + | sed 's/value: "openwebui-manifests"/value: "openwebui-manifests"\n - name: DRILL_BACKUP_OVERRIDE\n value: "no-such-backup"/' \ + | kubectl apply -f - +``` + +For a simpler failure drill, apply the CronJob-derived Job then patch env before scheduling — see the parent DEV-575 comment for the exact runbook. + +## RBAC scope + +- `velero.io/backups,restores`: get/list/create/delete in-cluster (namespace + scope is enforced by resource type — Backups/Restores live in `velero`). +- `namespaces`: cluster-wide get/list/create/delete. Cannot be name-scoped in + RBAC; the drill script enforces the `restore-test-` prefix before ever + deleting a namespace. +- `configmaps`: get/list/create/update/patch (only used to write the result + ConfigMap in `velero` — pod-level namespace scoping via ServiceAccount). +- Cluster-wide `view` ClusterRole for the counting diff. diff --git a/velero/schedules/00-cluster-all.yaml b/velero/schedules/00-cluster-all.yaml new file mode 100644 index 0000000..4d061be --- /dev/null +++ b/velero/schedules/00-cluster-all.yaml @@ -0,0 +1,24 @@ +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: cluster-all + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: cluster +spec: + schedule: "0 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - '*' + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: true + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default diff --git a/velero/schedules/10-namespace-manifests.yaml b/velero/schedules/10-namespace-manifests.yaml new file mode 100644 index 0000000..5e7c947 --- /dev/null +++ b/velero/schedules/10-namespace-manifests.yaml @@ -0,0 +1,780 @@ +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: argocd-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: argocd +spec: + schedule: "15 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - argocd + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: basicstack-web-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: basicstack-web +spec: + schedule: "16 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - basicstack-web + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: bookstack-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: bookstack +spec: + schedule: "17 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - bookstack + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: cert-manager-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: cert-manager +spec: + schedule: "18 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - cert-manager + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: container-registry-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: container-registry +spec: + schedule: "19 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - container-registry + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: directus-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: directus +spec: + schedule: "20 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - directus + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: dozzle-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: dozzle +spec: + schedule: "21 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - dozzle + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: forgejo-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: forgejo +spec: + schedule: "22 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - forgejo + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: grist-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: grist +spec: + schedule: "23 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - grist + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: harbor-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: harbor +spec: + schedule: "24 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - harbor + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: headlamp-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: headlamp +spec: + schedule: "25 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - headlamp + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: kaneo-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: kaneo +spec: + schedule: "26 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - kaneo + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: keycloak-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: keycloak +spec: + schedule: "27 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - keycloak + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: monitoring-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: monitoring +spec: + schedule: "28 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - monitoring + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: nextcloud-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: nextcloud +spec: + schedule: "29 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - nextcloud + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: observability-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: observability +spec: + schedule: "30 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - observability + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: opencloud-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: opencloud +spec: + schedule: "31 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - opencloud + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: openwebui-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: openwebui +spec: + schedule: "32 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - openwebui + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: outline-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: outline +spec: + schedule: "33 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - outline + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: pangolin-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: pangolin +spec: + schedule: "34 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - pangolin + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: paperclip-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: paperclip +spec: + schedule: "35 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - paperclip + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: paperclip-system-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: paperclip-system +spec: + schedule: "36 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - paperclip-system + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: passbolt-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: passbolt +spec: + schedule: "37 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - passbolt + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: planka-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: planka +spec: + schedule: "38 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - planka + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: platform-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: platform +spec: + schedule: "39 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - platform + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: pocket-id-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: pocket-id +spec: + schedule: "40 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - pocket-id + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: reloader-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: reloader +spec: + schedule: "41 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - reloader + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: stalwart-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: stalwart +spec: + schedule: "42 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - stalwart + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: vaultwarden-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: vaultwarden +spec: + schedule: "43 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - vaultwarden + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: vikunja-manifests + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-manifests + velero.basicstack.de/namespace: vikunja +spec: + schedule: "44 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 720h0m0s + includedNamespaces: + - vikunja + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: false + storageLocation: default diff --git a/velero/schedules/20-namespace-data.yaml b/velero/schedules/20-namespace-data.yaml new file mode 100644 index 0000000..07570c5 --- /dev/null +++ b/velero/schedules/20-namespace-data.yaml @@ -0,0 +1,104 @@ +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: pocket-id-data + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-data + velero.basicstack.de/namespace: pocket-id +spec: + schedule: "45 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 336h0m0s + includedNamespaces: + - pocket-id + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: true + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: vaultwarden-data + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-data + velero.basicstack.de/namespace: vaultwarden +spec: + schedule: "47 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 336h0m0s + includedNamespaces: + - vaultwarden + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: true + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: vikunja-data + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-data + velero.basicstack.de/namespace: vikunja +spec: + schedule: "49 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 336h0m0s + includedNamespaces: + - vikunja + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: true + storageLocation: default +--- +apiVersion: velero.io/v1 +kind: Schedule +metadata: + name: passbolt-data + namespace: velero + labels: + app.kubernetes.io/part-of: velero-schedules + tier: namespace-data + velero.basicstack.de/namespace: passbolt +spec: + schedule: "51 3 * * *" + useOwnerReferencesInBackup: false + template: + ttl: 336h0m0s + includedNamespaces: + - passbolt + excludedNamespaces: + - velero + - kube-system + - kube-node-lease + - kube-public + includeClusterResources: false + snapshotVolumes: false + defaultVolumesToFsBackup: true + storageLocation: default diff --git a/velero/servicemonitor.yaml b/velero/servicemonitor.yaml new file mode 100644 index 0000000..056a7d1 --- /dev/null +++ b/velero/servicemonitor.yaml @@ -0,0 +1,28 @@ +--- +# Scrape Velero's built-in Prometheus endpoint (http-monitoring / :8085) +# so kube-prometheus-stack can drive the accompanying PrometheusRule +# (velero/prometheusrule.yaml). The `release: kube-prometheus-stack` +# label is what the operator's serviceMonitorSelector matches. +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: velero + namespace: velero + labels: + app.kubernetes.io/name: velero + app.kubernetes.io/part-of: velero + release: kube-prometheus-stack +spec: + namespaceSelector: + matchNames: + - velero + selector: + matchLabels: + app.kubernetes.io/instance: velero + app.kubernetes.io/name: velero + endpoints: + - port: http-monitoring + path: /metrics + interval: 30s + scrapeTimeout: 10s + scheme: http