From 50a8588dc166573d89742958c069a24308a3b33d Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Tue, 11 Aug 2026 20:46:47 +1000 Subject: [PATCH 01/13] woodpecker: add jellyfin-ha-src CI ServiceAccount (#352) ## Why The jellyfin-ha-src Woodpecker build+test pipeline sets a `serviceAccountName` that must exist in the `woodpecker` namespace. ## How - Add a `jellyfin-ha-src` ServiceAccount under `apps/base/woodpecker/`, mirroring the `kea-operator-ci` build/test account (no Vault binding needed; the pipeline only restores/builds/tests). - Register it in the woodpecker kustomization. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/352 Co-authored-by: Ben Vincent Co-committed-by: Ben Vincent --- apps/base/woodpecker/kustomization.yaml | 1 + apps/base/woodpecker/serviceaccount_jellyfin_ha_src.yaml | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 apps/base/woodpecker/serviceaccount_jellyfin_ha_src.yaml diff --git a/apps/base/woodpecker/kustomization.yaml b/apps/base/woodpecker/kustomization.yaml index 6977aba..20564c7 100644 --- a/apps/base/woodpecker/kustomization.yaml +++ b/apps/base/woodpecker/kustomization.yaml @@ -8,6 +8,7 @@ resources: - cnpg_backup.yaml - cnpg_pooler.yaml - serviceaccount_kea_operator_ci.yaml + - serviceaccount_jellyfin_ha_src.yaml - serviceaccount_terraform_artifactapi.yaml - serviceaccount_terraform_authentik.yaml - serviceaccount_terraform_enc.yaml diff --git a/apps/base/woodpecker/serviceaccount_jellyfin_ha_src.yaml b/apps/base/woodpecker/serviceaccount_jellyfin_ha_src.yaml new file mode 100644 index 0000000..759e61a --- /dev/null +++ b/apps/base/woodpecker/serviceaccount_jellyfin_ha_src.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: jellyfin-ha-src + namespace: woodpecker From 99350c4bd62bd490b8775569752301e75781112a Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Wed, 12 Aug 2026 00:39:44 +1000 Subject: [PATCH 02/13] artifactapi: bump images to v3.9.1 (deb repo support + UI) (#353) ## Why artifactapi v3.9.1 ships Debian/apt repository support (local, remote, and metadata-only repos) plus the UI "how to use" deb sections. Roll it out to the deployed cluster. The v3.9.1 images are published/building from the tag. ## How - Bump api image `git.unkin.net/unkin/artifactapi` to `v3.9.1`. - Bump ui image `git.unkin.net/unkin/artifactapi-ui` to `v3.9.1`. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/353 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/artifactapi/api-deployment.yaml | 2 +- apps/base/artifactapi/ui-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base/artifactapi/api-deployment.yaml b/apps/base/artifactapi/api-deployment.yaml index 80bc87e..a4ba083 100644 --- a/apps/base/artifactapi/api-deployment.yaml +++ b/apps/base/artifactapi/api-deployment.yaml @@ -36,7 +36,7 @@ spec: mountPath: /combined-certs containers: - name: api - image: git.unkin.net/unkin/artifactapi:v3.8.0 + image: git.unkin.net/unkin/artifactapi:v3.9.1 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 diff --git a/apps/base/artifactapi/ui-deployment.yaml b/apps/base/artifactapi/ui-deployment.yaml index 38de490..44607f3 100644 --- a/apps/base/artifactapi/ui-deployment.yaml +++ b/apps/base/artifactapi/ui-deployment.yaml @@ -22,7 +22,7 @@ spec: automountServiceAccountToken: true containers: - name: ui - image: git.unkin.net/unkin/artifactapi-ui:v3.8.0 + image: git.unkin.net/unkin/artifactapi-ui:v3.9.1 imagePullPolicy: IfNotPresent ports: - containerPort: 80 From 51143c56b79e6cf7a0da6c01ba775592c6c6b461 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Wed, 12 Aug 2026 20:59:15 +1000 Subject: [PATCH 03/13] artifactapi: bump images to v3.10.0 (apk repo support + UI) (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why artifactapi v3.10.0 ships Alpine/apk support — an apk local repository, a `github_alpine` remote, and metadata-only handling — plus the new UI how-to sections. Rolling the deployed images forward from v3.9.1 delivers apk repo support and the UI docs to the cluster. ## What - Bump `apps/base/artifactapi/api-deployment.yaml` API image `git.unkin.net/unkin/artifactapi` from `v3.9.1` to `v3.10.0`. - Bump `apps/base/artifactapi/ui-deployment.yaml` UI image `git.unkin.net/unkin/artifactapi-ui` from `v3.9.1` to `v3.10.0`. The v3.10.0 images are published/building from the `v3.10.0` tag. Only the two image tags change; `kustomize build`, yamllint, and pre-commit all pass. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/354 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/artifactapi/api-deployment.yaml | 2 +- apps/base/artifactapi/ui-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base/artifactapi/api-deployment.yaml b/apps/base/artifactapi/api-deployment.yaml index a4ba083..6c76fcd 100644 --- a/apps/base/artifactapi/api-deployment.yaml +++ b/apps/base/artifactapi/api-deployment.yaml @@ -36,7 +36,7 @@ spec: mountPath: /combined-certs containers: - name: api - image: git.unkin.net/unkin/artifactapi:v3.9.1 + image: git.unkin.net/unkin/artifactapi:v3.10.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 diff --git a/apps/base/artifactapi/ui-deployment.yaml b/apps/base/artifactapi/ui-deployment.yaml index 44607f3..0a6a88c 100644 --- a/apps/base/artifactapi/ui-deployment.yaml +++ b/apps/base/artifactapi/ui-deployment.yaml @@ -22,7 +22,7 @@ spec: automountServiceAccountToken: true containers: - name: ui - image: git.unkin.net/unkin/artifactapi-ui:v3.9.1 + image: git.unkin.net/unkin/artifactapi-ui:v3.10.0 imagePullPolicy: IfNotPresent ports: - containerPort: 80 From 496b99f222cca82600ee52a2de40d61945c595f9 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Wed, 12 Aug 2026 23:54:34 +1000 Subject: [PATCH 04/13] artifactapi: bump image to v3.10.1 (#355) Ships the #117 local-repodata determinism fix (rpm + deb/apk) now that the v3.10.1 images are built and present in the Gitea registry. Simple one-release patch bump from the deployed v3.10.0 pin. How: - Bump artifactapi api image pin v3.10.0 -> v3.10.1 - Bump artifactapi-ui image pin v3.10.0 -> v3.10.1 Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/355 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/artifactapi/api-deployment.yaml | 2 +- apps/base/artifactapi/ui-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base/artifactapi/api-deployment.yaml b/apps/base/artifactapi/api-deployment.yaml index 6c76fcd..f0f13fb 100644 --- a/apps/base/artifactapi/api-deployment.yaml +++ b/apps/base/artifactapi/api-deployment.yaml @@ -36,7 +36,7 @@ spec: mountPath: /combined-certs containers: - name: api - image: git.unkin.net/unkin/artifactapi:v3.10.0 + image: git.unkin.net/unkin/artifactapi:v3.10.1 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 diff --git a/apps/base/artifactapi/ui-deployment.yaml b/apps/base/artifactapi/ui-deployment.yaml index 0a6a88c..6c17f3d 100644 --- a/apps/base/artifactapi/ui-deployment.yaml +++ b/apps/base/artifactapi/ui-deployment.yaml @@ -22,7 +22,7 @@ spec: automountServiceAccountToken: true containers: - name: ui - image: git.unkin.net/unkin/artifactapi-ui:v3.10.0 + image: git.unkin.net/unkin/artifactapi-ui:v3.10.1 imagePullPolicy: IfNotPresent ports: - containerPort: 80 From d37e87f35b0c4ba93b7b4322cc46e8eb10744082 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Thu, 13 Aug 2026 17:40:41 +1000 Subject: [PATCH 05/13] artifactapi: bump image to v3.11.0 (#356) ## Why Ship artifactapi v3.11.0: mirrorlist support, least-connections balancing, and cache-flush. Both `artifactapi` and `artifactapi-ui` images are built and present in the Gitea registry (release pipeline green). ## How Bump the api and ui image pins v3.10.1 -> v3.11.0: - `apps/base/artifactapi/api-deployment.yaml`: `git.unkin.net/unkin/artifactapi:v3.10.1` -> `:v3.11.0` - `apps/base/artifactapi/ui-deployment.yaml`: `git.unkin.net/unkin/artifactapi-ui:v3.10.1` -> `:v3.11.0` Overlay `apps/overlays/au-syd1/artifactapi` renders clean via `kustomize build`. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/356 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/artifactapi/api-deployment.yaml | 2 +- apps/base/artifactapi/ui-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base/artifactapi/api-deployment.yaml b/apps/base/artifactapi/api-deployment.yaml index f0f13fb..72e46d8 100644 --- a/apps/base/artifactapi/api-deployment.yaml +++ b/apps/base/artifactapi/api-deployment.yaml @@ -36,7 +36,7 @@ spec: mountPath: /combined-certs containers: - name: api - image: git.unkin.net/unkin/artifactapi:v3.10.1 + image: git.unkin.net/unkin/artifactapi:v3.11.0 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 diff --git a/apps/base/artifactapi/ui-deployment.yaml b/apps/base/artifactapi/ui-deployment.yaml index 6c17f3d..b9f2437 100644 --- a/apps/base/artifactapi/ui-deployment.yaml +++ b/apps/base/artifactapi/ui-deployment.yaml @@ -22,7 +22,7 @@ spec: automountServiceAccountToken: true containers: - name: ui - image: git.unkin.net/unkin/artifactapi-ui:v3.10.1 + image: git.unkin.net/unkin/artifactapi-ui:v3.11.0 imagePullPolicy: IfNotPresent ports: - containerPort: 80 From 4c8a773cd87d7f65379d494cabc7f6dd92a86b4e Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Thu, 13 Aug 2026 20:04:22 +1000 Subject: [PATCH 06/13] artifactapi: bump image to v3.11.1 (#358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Ships the least_conn selection optimization released in artifactapi v3.11.1 (image built, pushed to the registry, pipeline green). ## How - Bump api pin: `artifactapi:v3.11.0` → `:v3.11.1` (apps/base/artifactapi/api-deployment.yaml) - Bump ui pin: `artifactapi-ui:v3.11.0` → `:v3.11.1` (apps/base/artifactapi/ui-deployment.yaml) `kustomize build apps/overlays/au-syd1/artifactapi` clean; both images resolve to v3.11.1. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/358 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/artifactapi/api-deployment.yaml | 2 +- apps/base/artifactapi/ui-deployment.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/base/artifactapi/api-deployment.yaml b/apps/base/artifactapi/api-deployment.yaml index 72e46d8..afda59d 100644 --- a/apps/base/artifactapi/api-deployment.yaml +++ b/apps/base/artifactapi/api-deployment.yaml @@ -36,7 +36,7 @@ spec: mountPath: /combined-certs containers: - name: api - image: git.unkin.net/unkin/artifactapi:v3.11.0 + image: git.unkin.net/unkin/artifactapi:v3.11.1 imagePullPolicy: IfNotPresent ports: - containerPort: 8000 diff --git a/apps/base/artifactapi/ui-deployment.yaml b/apps/base/artifactapi/ui-deployment.yaml index b9f2437..1819ed2 100644 --- a/apps/base/artifactapi/ui-deployment.yaml +++ b/apps/base/artifactapi/ui-deployment.yaml @@ -22,7 +22,7 @@ spec: automountServiceAccountToken: true containers: - name: ui - image: git.unkin.net/unkin/artifactapi-ui:v3.11.0 + image: git.unkin.net/unkin/artifactapi-ui:v3.11.1 imagePullPolicy: IfNotPresent ports: - containerPort: 80 From fa1f3e77561fed8b472e34d144d9268422150555 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Thu, 13 Aug 2026 20:08:30 +1000 Subject: [PATCH 07/13] add ghp (GitHub proxy) deployment (#357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Deploy `goodtune/ghp` (a GitHub proxy) so estate agents have a controlled GitHub access path. Mirrors the artifactapi app structure and the estate's templated `default` Vault convention. ## How (this PR) - Namespace `ghp`. Runs as the namespace's auto-created `default` ServiceAccount (no custom SA). - CNPG `Cluster` (named `postgres`, db/owner `ghp`) + dedicated `cnpg-ghp` Ceph RGW backup bucket + nightly ScheduledBackup. - Deployment `command: ["/ghp","serve"]`; `GHP_DATABASE_DSN` assembled from the CNPG-generated `postgres-app` Secret via `$(VAR)` expansion pointing at `postgres-rw.ghp.svc`. - **Sync-phase migrate hook Job** `ghp-migrate` (`/ghp migrate`) at sync-wave 1 so serve replicas never race migrations. - VSO wiring on the templated `default` role: `VaultAuth` role/SA `default`; `VaultStaticSecret`s source `kv/kubernetes/namespace/ghp/default/github-app` → Secret `ghp-github-app` and `kv/kubernetes/namespace/ghp/default/app` → Secret `ghp-app`. `private_key` mounted as a file; `encryption_key` → `GHP_ENCRYPTION_KEY`. - cert-manager `Certificate` `ghp-tls` off ClusterIssuer `vault-issuer`, SANs github.com/api.github.com/codeload.github.com/*.githubcopilot.com/ghp.k8s.syd1.au.unkin.net; mounted for `GHP_TLS_CERT_FILE`/`GHP_TLS_KEY_FILE`. - Service (443->8443, 80->8080, metrics 9136), PDB, VMServiceScrape, internal Traefik Gateway + HTTPRoute for `ghp.k8s.syd1.au.unkin.net`. - Sync-wave ordering: DB + VSO/cert (wave 0, wait Healthy) -> migrate Job (wave 1) -> serve Deployment + Service/Gateway/HTTPRoute (wave 2). Writable `/tmp` emptyDir on both pods (root FS is read-only). - Registered in the `platform` ApplicationSet + AppProject. - Validation: `kustomize build` clean, kubeconform 17/17 valid (0 skipped), yamllint + pre-commit clean. ## NOTE — prerequisites before ghp goes Healthy - **`GHP_ADMINS` placeholder**: `apps/base/ghp/configmap.yaml` has `GHP_ADMINS: "REPLACE_ME_ben_github_username"` — set it to Ben's GitHub username. - Ben's GitHub-App runbook + kv population at the templated paths `kv/kubernetes/namespace/ghp/default/github-app` (app_id/client_id/client_secret/private_key) and `kv/kubernetes/namespace/ghp/default/app` (encryption_key). - tf-artifactapi #25: the `ghcr` remote pattern for pulling `ghcr/goodtune/ghp`. - **No terraform-vault change needed** — the estate's templated `default` policy already grants read on `kv/kubernetes/namespace//default/*` (the bespoke ghp role PR terraform-vault#120 is closed). - DNS overrides (pointing github.com etc. at ghp) are intentionally **out of scope** — separate deferred bind-operator PR. Assumptions flagged: metrics env var `GHP_METRICS_LISTEN`; two Secrets (`ghp-github-app` + `ghp-app`) rather than one merged Secret; DSN assumes the CNPG-generated password is URL-safe. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/357 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/ghp/certificate.yaml | 28 ++++ apps/base/ghp/cnpg_backup.yaml | 50 +++++++ apps/base/ghp/cnpg_cluster.yaml | 117 +++++++++++++++ apps/base/ghp/configmap.yaml | 22 +++ apps/base/ghp/deployment.yaml | 145 +++++++++++++++++++ apps/base/ghp/gateway.yaml | 39 +++++ apps/base/ghp/httproute.yaml | 31 ++++ apps/base/ghp/kustomization.yaml | 19 +++ apps/base/ghp/migrate-job.yaml | 78 ++++++++++ apps/base/ghp/namespace.yaml | 5 + apps/base/ghp/pdb.yaml | 13 ++ apps/base/ghp/service.yaml | 29 ++++ apps/base/ghp/vaultauth.yaml | 20 +++ apps/base/ghp/vaultstaticsecret.yaml | 44 ++++++ apps/base/ghp/vmservicescrape.yaml | 18 +++ apps/overlays/au-syd1/ghp/kustomization.yaml | 6 + argocd/applicationsets/platform.yaml | 1 + argocd/projects/platform.yaml | 2 + 18 files changed, 667 insertions(+) create mode 100644 apps/base/ghp/certificate.yaml create mode 100644 apps/base/ghp/cnpg_backup.yaml create mode 100644 apps/base/ghp/cnpg_cluster.yaml create mode 100644 apps/base/ghp/configmap.yaml create mode 100644 apps/base/ghp/deployment.yaml create mode 100644 apps/base/ghp/gateway.yaml create mode 100644 apps/base/ghp/httproute.yaml create mode 100644 apps/base/ghp/kustomization.yaml create mode 100644 apps/base/ghp/migrate-job.yaml create mode 100644 apps/base/ghp/namespace.yaml create mode 100644 apps/base/ghp/pdb.yaml create mode 100644 apps/base/ghp/service.yaml create mode 100644 apps/base/ghp/vaultauth.yaml create mode 100644 apps/base/ghp/vaultstaticsecret.yaml create mode 100644 apps/base/ghp/vmservicescrape.yaml create mode 100644 apps/overlays/au-syd1/ghp/kustomization.yaml diff --git a/apps/base/ghp/certificate.yaml b/apps/base/ghp/certificate.yaml new file mode 100644 index 0000000..3e644ae --- /dev/null +++ b/apps/base/ghp/certificate.yaml @@ -0,0 +1,28 @@ +--- +# Serving cert off the internal Vault-PKI CA (agents already trust vault-ca-cert). +# SANs cover the GitHub endpoints ghp impersonates plus its own management host. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: ghp-tls + namespace: ghp + labels: + app.kubernetes.io/name: ghp + app.kubernetes.io/instance: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + secretName: ghp-tls + issuerRef: + kind: ClusterIssuer + name: vault-issuer + commonName: ghp.k8s.syd1.au.unkin.net + dnsNames: + - github.com + - api.github.com + - codeload.github.com + - "*.githubcopilot.com" + - ghp.k8s.syd1.au.unkin.net + privateKey: + algorithm: RSA + size: 4096 diff --git a/apps/base/ghp/cnpg_backup.yaml b/apps/base/ghp/cnpg_backup.yaml new file mode 100644 index 0000000..c0df5d6 --- /dev/null +++ b/apps/base/ghp/cnpg_backup.yaml @@ -0,0 +1,50 @@ +--- +# Ceph RGW (S3) backup target for the ghp CNPG cluster, provisioned by the +# in-estate cephrgw-operator. One dedicated bucket + owner user per cluster. +apiVersion: ceph.unkin.net/v1alpha1 +kind: ObjectStoreUser +metadata: + name: cnpg-ghp-backup + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + displayName: "CNPG backup owner (ghp)" + uid: cnpg-ghp-backup + maxBuckets: 5 + secretName: cnpg-ghp-backup-s3 + retainOnDelete: true +--- +apiVersion: ceph.unkin.net/v1alpha1 +kind: Bucket +metadata: + name: cnpg-ghp + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + placementTarget: ec + bucketName: cnpg-ghp + ownerRef: cnpg-ghp-backup + versioning: false + tags: + app: ghp + purpose: cnpg-backup + retainOnDelete: true +--- +# Nightly base backup; continuous WAL archiving is always-on via the Cluster's +# spec.backup.barmanObjectStore. Staggered off other clusters' schedules. +apiVersion: postgresql.cnpg.io/v1 +kind: ScheduledBackup +metadata: + name: cnpg-ghp-nightly + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + schedule: "0 50 1 * * *" + immediate: false + backupOwnerReference: self + method: barmanObjectStore + cluster: + name: postgres diff --git a/apps/base/ghp/cnpg_cluster.yaml b/apps/base/ghp/cnpg_cluster.yaml new file mode 100644 index 0000000..76a01d0 --- /dev/null +++ b/apps/base/ghp/cnpg_cluster.yaml @@ -0,0 +1,117 @@ +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: postgres + namespace: ghp + annotations: + # Wave 0: DB (and the generated postgres-app Secret) must be Healthy before + # the wave-1 migrate Job runs. ArgoCD gates on the Cluster's health status. + argocd.argoproj.io/sync-wave: "0" +spec: + affinity: + podAntiAffinityType: preferred + backup: + retentionPolicy: 30d + barmanObjectStore: + destinationPath: s3://cnpg-ghp + endpointURL: https://s3.ceph.unkin.net + endpointCA: + name: vault-ca-cert + key: ca.crt + s3Credentials: + accessKeyId: + name: cnpg-ghp-backup-s3 + key: AWS_ACCESS_KEY_ID + secretAccessKey: + name: cnpg-ghp-backup-s3 + key: AWS_SECRET_ACCESS_KEY + serverName: ghp + data: + compression: bzip2 + jobs: 2 + wal: + compression: zstd + maxParallel: 2 + bootstrap: + initdb: + # No secret ref: CNPG mints the owner credentials and publishes them in the + # generated "postgres-app" Secret, which the Deployment reads to build the DSN. + database: ghp + encoding: UTF8 + localeCType: C + localeCollate: C + owner: ghp + enablePDB: true + enableSuperuserAccess: false + failoverDelay: 0 + imageName: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie + instances: 3 + logLevel: info + maxSyncReplicas: 0 + minSyncReplicas: 0 + monitoring: + customQueriesConfigMap: + - key: queries + name: cnpg-default-monitoring + disableDefaultQueries: false + enablePodMonitor: false + postgresql: + parameters: + archive_mode: "on" + archive_timeout: 5min + dynamic_shared_memory_type: posix + effective_cache_size: 256MB + full_page_writes: "on" + log_destination: csvlog + log_directory: /controller/log + log_filename: postgres + log_rotation_age: "0" + log_rotation_size: "0" + log_truncate_on_rotation: "false" + logging_collector: "on" + max_connections: "200" + max_parallel_workers: "16" + max_replication_slots: "16" + max_worker_processes: "16" + shared_buffers: 128MB + shared_memory_type: mmap + ssl_max_protocol_version: TLSv1.3 + ssl_min_protocol_version: TLSv1.3 + wal_keep_size: 256MB + wal_level: logical + wal_log_hints: "on" + wal_receiver_timeout: 5s + wal_sender_timeout: 5s + syncReplicaElectionConstraint: + enabled: false + primaryUpdateMethod: restart + primaryUpdateStrategy: unsupervised + probes: + liveness: + isolationCheck: + connectionTimeout: 1000 + enabled: true + requestTimeout: 1000 + replicationSlots: + highAvailability: + enabled: true + slotPrefix: _cnpg_ + synchronizeReplicas: + enabled: true + updateInterval: 30 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 50m + memory: 256Mi + smartShutdownTimeout: 180 + startDelay: 3600 + stopDelay: 1800 + storage: + resizeInUseVolumes: true + size: 20Gi + storageClass: cephrbd-fast-delete + switchoverDelay: 3600 diff --git a/apps/base/ghp/configmap.yaml b/apps/base/ghp/configmap.yaml new file mode 100644 index 0000000..f35f4ed --- /dev/null +++ b/apps/base/ghp/configmap.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ghp-env + namespace: ghp +data: + GHP_DATABASE_DRIVER: postgres + # nonroot cannot bind <1024; listen high and remap in the Service. + GHP_SERVER_HTTPS_LISTEN: ":8443" + GHP_SERVER_HTTP_LISTEN: ":8080" + GHP_METRICS_LISTEN: ":9136" + GHP_METRICS_ENABLED: "true" + GHP_SERVER_BASE_URL: https://ghp.k8s.syd1.au.unkin.net + GHP_SERVER_MANAGEMENT_HOST: ghp.k8s.syd1.au.unkin.net + # private_key key from the ghp-github-app Secret, mounted as a file. + GHP_GITHUB_PRIVATE_KEY_FILE: /etc/ghp/github-app/private_key + # cert-manager Certificate ghp-tls, mounted from the ghp-tls Secret. + GHP_TLS_CERT_FILE: /etc/ghp/tls/tls.crt + GHP_TLS_KEY_FILE: /etc/ghp/tls/tls.key + # PLACEHOLDER: set to Ben's GitHub username before ghp will admit an admin. + GHP_ADMINS: "REPLACE_ME_ben_github_username" diff --git a/apps/base/ghp/deployment.yaml b/apps/base/ghp/deployment.yaml new file mode 100644 index 0000000..ab4013b --- /dev/null +++ b/apps/base/ghp/deployment.yaml @@ -0,0 +1,145 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ghp + namespace: ghp + annotations: + # Wave 2: serve only after the wave-1 migrate Job completes. + argocd.argoproj.io/sync-wave: "2" + configmap.reloader.stakater.com/auto: "true" + secret.reloader.stakater.com/reload: "ghp-github-app,ghp-app,ghp-tls,postgres-app" +spec: + replicas: 2 + selector: + matchLabels: + app: ghp + strategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: ghp + spec: + serviceAccountName: default + automountServiceAccountToken: true + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + containers: + - name: ghp + image: artifactapi.k8s.syd1.au.unkin.net/ghcr/goodtune/ghp:0.20.0 + imagePullPolicy: IfNotPresent + # Drop the image's default --migrate so replicas never race migrations; + # schema is applied by the wave-1 migrate hook Job instead. + command: ["/ghp", "serve"] + ports: + - containerPort: 8443 + name: https + protocol: TCP + - containerPort: 8080 + name: http + protocol: TCP + - containerPort: 9136 + name: metrics + protocol: TCP + envFrom: + - configMapRef: + name: ghp-env + optional: false + env: + # DSN assembled from the CNPG-generated postgres-app Secret; $(VAR) + # expansion resolves the two env entries defined above it. + - name: GHP_DB_USER + valueFrom: + secretKeyRef: + name: postgres-app + key: username + - name: GHP_DB_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-app + key: password + - name: GHP_DATABASE_DSN + value: "postgres://$(GHP_DB_USER):$(GHP_DB_PASSWORD)@postgres-rw.ghp.svc:5432/ghp?sslmode=require" + - name: GHP_GITHUB_APP_ID + valueFrom: + secretKeyRef: + name: ghp-github-app + key: app_id + - name: GHP_GITHUB_CLIENT_ID + valueFrom: + secretKeyRef: + name: ghp-github-app + key: client_id + - name: GHP_GITHUB_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: ghp-github-app + key: client_secret + - name: GHP_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: ghp-app + key: encryption_key + volumeMounts: + - name: github-app + mountPath: /etc/ghp/github-app + readOnly: true + - name: tls + mountPath: /etc/ghp/tls + readOnly: true + - name: tmp + mountPath: /tmp + livenessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: metrics + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: metrics + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + limits: + cpu: "2" + memory: 2Gi + requests: + cpu: "1" + memory: 512Mi + volumes: + - name: github-app + secret: + secretName: ghp-github-app + - name: tls + secret: + secretName: ghp-tls + # Writable scratch: root FS is read-only. Disk-backed (not memory medium) + # so codeload tarball staging doesn't count against the pod memory limit. + - name: tmp + emptyDir: + sizeLimit: 2Gi + restartPolicy: Always diff --git a/apps/base/ghp/gateway.yaml b/apps/base/ghp/gateway.yaml new file mode 100644 index 0000000..80fb011 --- /dev/null +++ b/apps/base/ghp/gateway.yaml @@ -0,0 +1,39 @@ +--- +# Management UI ingress for ghp.k8s.syd1.au.unkin.net via the internal Traefik. +# TLS is terminated with the ghp-tls Secret produced by the cert-manager +# Certificate (which also carries the GitHub SANs); no cert-manager annotation +# here so the two never fight over the same Secret. +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + labels: + traefik.io/instance: internal + annotations: + argocd.argoproj.io/sync-wave: "2" + external-dns.alpha.kubernetes.io/hostname: ghp.k8s.syd1.au.unkin.net + external-dns.alpha.kubernetes.io/target: 198.18.200.4 + name: ghp + namespace: ghp +spec: + gatewayClassName: traefik-internal + listeners: + - allowedRoutes: + namespaces: + from: Same + hostname: ghp.k8s.syd1.au.unkin.net + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + hostname: ghp.k8s.syd1.au.unkin.net + name: https + port: 443 + protocol: HTTPS + tls: + certificateRefs: + - group: "" + kind: Secret + name: ghp-tls + mode: Terminate diff --git a/apps/base/ghp/httproute.yaml b/apps/base/ghp/httproute.yaml new file mode 100644 index 0000000..af65a04 --- /dev/null +++ b/apps/base/ghp/httproute.yaml @@ -0,0 +1,31 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ghp-route + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + hostnames: + - ghp.k8s.syd1.au.unkin.net + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: ghp + sectionName: http + - group: gateway.networking.k8s.io + kind: Gateway + name: ghp + sectionName: https + rules: + - backendRefs: + - group: "" + kind: Service + name: ghp + port: 80 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/apps/base/ghp/kustomization.yaml b/apps/base/ghp/kustomization.yaml new file mode 100644 index 0000000..a4db15f --- /dev/null +++ b/apps/base/ghp/kustomization.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + - configmap.yaml + - cnpg_cluster.yaml + - cnpg_backup.yaml + - vaultauth.yaml + - vaultstaticsecret.yaml + - certificate.yaml + - deployment.yaml + - migrate-job.yaml + - service.yaml + - pdb.yaml + - vmservicescrape.yaml + - gateway.yaml + - httproute.yaml diff --git a/apps/base/ghp/migrate-job.yaml b/apps/base/ghp/migrate-job.yaml new file mode 100644 index 0000000..443feef --- /dev/null +++ b/apps/base/ghp/migrate-job.yaml @@ -0,0 +1,78 @@ +--- +# Runs the schema migration once per sync, before the Deployment rolls, so the +# serve replicas never race migrations. Deleted before each re-create so a new +# image/version re-runs it. +# +# Sync-phase hook at wave 1 (NOT PreSync): the CNPG Cluster + generated +# postgres-app Secret apply at wave 0 and ArgoCD waits for the Cluster to be +# Healthy before starting wave 1, so on a fresh install Postgres exists before +# migrate connects. (A PreSync hook would run before the Sync phase that creates +# the DB, deadlocking the first install.) +apiVersion: batch/v1 +kind: Job +metadata: + name: ghp-migrate + namespace: ghp + annotations: + argocd.argoproj.io/hook: Sync + argocd.argoproj.io/hook-delete-policy: BeforeHookCreation + argocd.argoproj.io/sync-wave: "1" +spec: + backoffLimit: 6 + ttlSecondsAfterFinished: 600 + template: + metadata: + labels: + app: ghp-migrate + spec: + serviceAccountName: default + automountServiceAccountToken: true + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + containers: + - name: migrate + image: artifactapi.k8s.syd1.au.unkin.net/ghcr/goodtune/ghp:0.20.0 + imagePullPolicy: IfNotPresent + command: ["/ghp", "migrate"] + env: + - name: GHP_DATABASE_DRIVER + value: postgres + - name: GHP_DB_USER + valueFrom: + secretKeyRef: + name: postgres-app + key: username + - name: GHP_DB_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-app + key: password + - name: GHP_DATABASE_DSN + value: "postgres://$(GHP_DB_USER):$(GHP_DB_PASSWORD)@postgres-rw.ghp.svc:5432/ghp?sslmode=require" + volumeMounts: + - name: tmp + mountPath: /tmp + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + limits: + cpu: "1" + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + volumes: + # Writable scratch: root FS is read-only, so give the migrator a /tmp. + - name: tmp + emptyDir: + sizeLimit: 256Mi diff --git a/apps/base/ghp/namespace.yaml b/apps/base/ghp/namespace.yaml new file mode 100644 index 0000000..ce965f8 --- /dev/null +++ b/apps/base/ghp/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ghp diff --git a/apps/base/ghp/pdb.yaml b/apps/base/ghp/pdb.yaml new file mode 100644 index 0000000..4a47f6d --- /dev/null +++ b/apps/base/ghp/pdb.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: ghp + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + minAvailable: 1 + selector: + matchLabels: + app: ghp diff --git a/apps/base/ghp/service.yaml b/apps/base/ghp/service.yaml new file mode 100644 index 0000000..7c3e1d3 --- /dev/null +++ b/apps/base/ghp/service.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: ghp + namespace: ghp + labels: + app: ghp + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + internalTrafficPolicy: Cluster + ports: + - name: https + port: 443 + protocol: TCP + targetPort: https + - name: http + port: 80 + protocol: TCP + targetPort: http + - name: metrics + port: 9136 + protocol: TCP + targetPort: metrics + selector: + app: ghp + sessionAffinity: None + type: ClusterIP diff --git a/apps/base/ghp/vaultauth.yaml b/apps/base/ghp/vaultauth.yaml new file mode 100644 index 0000000..05a1955 --- /dev/null +++ b/apps/base/ghp/vaultauth.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultAuth +metadata: + name: default + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + allowedNamespaces: + - ghp + kubernetes: + audiences: + - vault + role: default + serviceAccount: default + tokenExpirationSeconds: 600 + method: kubernetes + mount: k8s/au/syd1 + vaultConnectionRef: vso-system/default diff --git a/apps/base/ghp/vaultstaticsecret.yaml b/apps/base/ghp/vaultstaticsecret.yaml new file mode 100644 index 0000000..1c454f3 --- /dev/null +++ b/apps/base/ghp/vaultstaticsecret.yaml @@ -0,0 +1,44 @@ +--- +# GitHub App credentials (app_id, client_id, client_secret, private_key). Ben +# populates kv/kubernetes/namespace/ghp/default/github-app via the runbook; the +# default k8s role's templated policy already grants read here, so no +# terraform-vault change is needed. VSO syncs it into the ghp-github-app Secret. +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: ghp-github-app + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + destination: + create: true + name: ghp-github-app + overwrite: true + hmacSecretData: true + mount: kv + path: kubernetes/namespace/ghp/default/github-app + refreshAfter: 5m + type: kv-v2 + vaultAuthRef: default +--- +# Application encryption key (encryption_key) from +# kv/kubernetes/namespace/ghp/default/app. +apiVersion: secrets.hashicorp.com/v1beta1 +kind: VaultStaticSecret +metadata: + name: ghp-app + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + destination: + create: true + name: ghp-app + overwrite: true + hmacSecretData: true + mount: kv + path: kubernetes/namespace/ghp/default/app + refreshAfter: 5m + type: kv-v2 + vaultAuthRef: default diff --git a/apps/base/ghp/vmservicescrape.yaml b/apps/base/ghp/vmservicescrape.yaml new file mode 100644 index 0000000..a91bde9 --- /dev/null +++ b/apps/base/ghp/vmservicescrape.yaml @@ -0,0 +1,18 @@ +--- +# Scrape ghp's Prometheus metrics endpoint. Picked up by the observability +# VMAgent (selectAllByDefault). Estate uses VictoriaMetrics, so VMServiceScrape +# rather than a prometheus-operator ServiceMonitor. +apiVersion: operator.victoriametrics.com/v1beta1 +kind: VMServiceScrape +metadata: + name: ghp + namespace: ghp + annotations: + argocd.argoproj.io/sync-wave: "2" +spec: + selector: + matchLabels: + app: ghp + endpoints: + - port: metrics + path: /metrics diff --git a/apps/overlays/au-syd1/ghp/kustomization.yaml b/apps/overlays/au-syd1/ghp/kustomization.yaml new file mode 100644 index 0000000..80a5cdb --- /dev/null +++ b/apps/overlays/au-syd1/ghp/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/ghp diff --git a/argocd/applicationsets/platform.yaml b/argocd/applicationsets/platform.yaml index 5508107..22f8e08 100644 --- a/argocd/applicationsets/platform.yaml +++ b/argocd/applicationsets/platform.yaml @@ -26,6 +26,7 @@ spec: - path: apps/overlays/*/elastic-system - path: apps/overlays/*/encapi - path: apps/overlays/*/externaldns + - path: apps/overlays/*/ghp - path: apps/overlays/*/gitea - path: apps/overlays/*/grafana-system - path: apps/overlays/*/inteldeviceplugins-system diff --git a/argocd/projects/platform.yaml b/argocd/projects/platform.yaml index e1348a7..9c0ef74 100644 --- a/argocd/projects/platform.yaml +++ b/argocd/projects/platform.yaml @@ -39,6 +39,8 @@ spec: server: https://kubernetes.default.svc - namespace: 'externaldns' server: https://kubernetes.default.svc + - namespace: 'ghp' + server: https://kubernetes.default.svc - namespace: 'gitea' server: https://kubernetes.default.svc - namespace: 'jfrog' From 0130d538f58bb13af9317b28ef40ee0feec6c1c7 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Thu, 13 Aug 2026 22:08:05 +1000 Subject: [PATCH 08/13] ghp: use direct ghcr.io image + set GHP_ADMINS (#359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why The `ghp` app is deployed but its pods are stuck 0/1 Ready (and were ImagePullBackOff), for three separate reasons this PR fixes: - **ImagePullBackOff:** kubelet anonymous pulls fail on the artifactapi ghcr pull-through because ghcr.io's per-scope token auth is not proxied. The direct public image pulls anonymously, so switch to it. - **Pods never Ready:** ghp serves its metrics endpoint over **HTTPS** (TLS is configured globally), but the liveness/readiness probes used the default HTTP scheme, so the kubelet probe got an HTTPS-server error and the pods never went Ready. - **Scrape failure:** the VMServiceScrape hits that same HTTPS endpoint and needs a matching scheme/TLS config, or VM scraping of ghp fails. - The `GHP_ADMINS` value was still a placeholder. ## How - `deployment.yaml`: image -> `ghcr.io/goodtune/ghp:0.20.0`; liveness + readiness probe `scheme: HTTP` -> `HTTPS` (kubelet does not verify the probe cert). - `migrate-job.yaml`: image -> `ghcr.io/goodtune/ghp:0.20.0` (shared image). - `vmservicescrape.yaml`: endpoint `scheme: https` + `tlsConfig.insecureSkipVerify: true` (internal-CA cert; pod-IP target not in SANs). - `configmap.yaml`: `GHP_ADMINS` -> `neoloc`. Validated: `kustomize build apps/overlays/au-syd1/ghp` renders clean, kubeconform + pre-commit pass. Not applied. ## Follow-up (not fixed here) The artifactapi ghcr pull-through does not proxy ghcr.io's per-scope token auth for anonymous kubelet pulls — worth closing that gap so estate images can go back through artifactapi. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/359 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/ghp/configmap.yaml | 2 +- apps/base/ghp/deployment.yaml | 6 +++--- apps/base/ghp/migrate-job.yaml | 2 +- apps/base/ghp/vmservicescrape.yaml | 5 +++++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/base/ghp/configmap.yaml b/apps/base/ghp/configmap.yaml index f35f4ed..4cc2947 100644 --- a/apps/base/ghp/configmap.yaml +++ b/apps/base/ghp/configmap.yaml @@ -19,4 +19,4 @@ data: GHP_TLS_CERT_FILE: /etc/ghp/tls/tls.crt GHP_TLS_KEY_FILE: /etc/ghp/tls/tls.key # PLACEHOLDER: set to Ben's GitHub username before ghp will admit an admin. - GHP_ADMINS: "REPLACE_ME_ben_github_username" + GHP_ADMINS: "neoloc" diff --git a/apps/base/ghp/deployment.yaml b/apps/base/ghp/deployment.yaml index ab4013b..1a7f5b2 100644 --- a/apps/base/ghp/deployment.yaml +++ b/apps/base/ghp/deployment.yaml @@ -34,7 +34,7 @@ spec: type: RuntimeDefault containers: - name: ghp - image: artifactapi.k8s.syd1.au.unkin.net/ghcr/goodtune/ghp:0.20.0 + image: ghcr.io/goodtune/ghp:0.20.0 imagePullPolicy: IfNotPresent # Drop the image's default --migrate so replicas never race migrations; # schema is applied by the wave-1 migrate hook Job instead. @@ -102,7 +102,7 @@ spec: httpGet: path: /metrics port: metrics - scheme: HTTP + scheme: HTTPS initialDelaySeconds: 30 periodSeconds: 30 successThreshold: 1 @@ -112,7 +112,7 @@ spec: httpGet: path: /metrics port: metrics - scheme: HTTP + scheme: HTTPS initialDelaySeconds: 10 periodSeconds: 5 successThreshold: 1 diff --git a/apps/base/ghp/migrate-job.yaml b/apps/base/ghp/migrate-job.yaml index 443feef..ef8438c 100644 --- a/apps/base/ghp/migrate-job.yaml +++ b/apps/base/ghp/migrate-job.yaml @@ -37,7 +37,7 @@ spec: type: RuntimeDefault containers: - name: migrate - image: artifactapi.k8s.syd1.au.unkin.net/ghcr/goodtune/ghp:0.20.0 + image: ghcr.io/goodtune/ghp:0.20.0 imagePullPolicy: IfNotPresent command: ["/ghp", "migrate"] env: diff --git a/apps/base/ghp/vmservicescrape.yaml b/apps/base/ghp/vmservicescrape.yaml index a91bde9..e5ca485 100644 --- a/apps/base/ghp/vmservicescrape.yaml +++ b/apps/base/ghp/vmservicescrape.yaml @@ -16,3 +16,8 @@ spec: endpoints: - port: metrics path: /metrics + scheme: https + # ghp serves metrics over TLS with an internal-CA cert; skip verification + # since the scrape targets a pod IP the cert SANs do not cover. + tlsConfig: + insecureSkipVerify: true From 5e2eecda6b87272e1f063c76f236c013554945c0 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Thu, 13 Aug 2026 22:48:23 +1000 Subject: [PATCH 09/13] ghp: serve at ghp.unkin.net (gateway + cert + httproute + DNS) (#360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why Make `ghp.unkin.net` the primary URL that the ghp app responds to. Until now ghp has only been reachable at the internal admin name `ghp.k8s.syd1.au.unkin.net`; this promotes the apex `unkin.net` name to canonical (mirroring the gitea apex pattern) while keeping the k8s name as the admin/internal route. ## How - **`apps/base/ghp/gateway.yaml`** — add `ghp.unkin.net` as the PRIMARY http/https listeners (`http-primary`/`https-primary`) and keep `ghp.k8s.syd1.au.unkin.net` as the admin route (`http-admin`/`https-admin`), mirroring gitea. Add the cert-manager gateway annotations (`cluster-issuer: vault-issuer`, `common-name: ghp.unkin.net`, `private-key-size: "4096"`) so cert-manager mints a NEW `ghp-gateway-tls` Secret with CN `ghp.unkin.net` + a SAN per TLS listener hostname (so it also covers the k8s host). Both https listeners' `certificateRefs` point at `ghp-gateway-tls`. This is a separate Secret from `ghp-tls` (ghp's own GitHub-impersonation cert), so cert-manager never fights over it. external-dns annotation stays scoped to the k8s host only — the apex is not published by external-dns. - **`apps/base/ghp/httproute.yaml`** — add `ghp.unkin.net` to `hostnames`; update `parentRefs` to attach to the renamed `http-primary`/`http-admin`/`https-primary`/`https-admin` sections. - **`apps/base/ghp/configmap.yaml`** — set `GHP_SERVER_MANAGEMENT_HOST: ghp.unkin.net` and `GHP_SERVER_BASE_URL: https://ghp.unkin.net`, so ghp serves its mgmt UI on that Host and OAuth callbacks use the new base. - **`apps/base/bind-internal/authoritative/records.yaml`** — add a bind-operator `DNSRecord` `ghp-dns-internal` for `ghp.unkin.net` -> **A 198.18.200.4** (traefik-internal gateway VIP), mirroring the active `identity-dns-internal` record in the same file. ## ⚠️ NOTE — GitHub App OAuth callback must be updated on github.com The GitHub App's OAuth callback URL must be changed on github.com to **`https://ghp.unkin.net/auth/github/callback`**. It was set to the k8s host in the runbook; OAuth login will break until this is updated. ## Note on DNS authority The bind-operator apex `unkin.net` zone comments warn that some apex names may still be served by the puppet DNS master (records from PuppetDB), with the k8s bind cluster not yet confirmed as the live authority for `unkin.net`. This DNSRecord mirrors the already-active `identity`/`lb1` apex records in the same file, so it is consistent with them — but if the k8s bind cluster is not the live authority for `unkin.net`, `ghp.unkin.net` will not resolve from this Record alone and the puppet master record must be added instead. ## Validation `kustomize build` of the ghp and bind-internal overlays render clean; kubeconform (strict) valid; yamllint + pre-commit clean. Not applied. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/360 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- .../bind-internal/authoritative/records.yaml | 14 +++++ apps/base/ghp/configmap.yaml | 4 +- apps/base/ghp/gateway.yaml | 56 ++++++++++++++----- apps/base/ghp/httproute.yaml | 13 ++++- 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/apps/base/bind-internal/authoritative/records.yaml b/apps/base/bind-internal/authoritative/records.yaml index b97dbec..830e744 100644 --- a/apps/base/bind-internal/authoritative/records.yaml +++ b/apps/base/bind-internal/authoritative/records.yaml @@ -88,3 +88,17 @@ spec: ttl: 600 values: - 103.216.191.185 +--- +apiVersion: bind.unkin.net/v1alpha1 +kind: DNSRecord +metadata: + name: ghp-dns-internal + namespace: bind-internal +spec: + zoneRef: unkin-net + name: ghp + type: A + ttl: 600 + values: + # traefik-internal gateway VIP; the ghp Gateway serves ghp.unkin.net there. + - 198.18.200.4 diff --git a/apps/base/ghp/configmap.yaml b/apps/base/ghp/configmap.yaml index 4cc2947..ca0ae45 100644 --- a/apps/base/ghp/configmap.yaml +++ b/apps/base/ghp/configmap.yaml @@ -11,8 +11,8 @@ data: GHP_SERVER_HTTP_LISTEN: ":8080" GHP_METRICS_LISTEN: ":9136" GHP_METRICS_ENABLED: "true" - GHP_SERVER_BASE_URL: https://ghp.k8s.syd1.au.unkin.net - GHP_SERVER_MANAGEMENT_HOST: ghp.k8s.syd1.au.unkin.net + GHP_SERVER_BASE_URL: https://ghp.unkin.net + GHP_SERVER_MANAGEMENT_HOST: ghp.unkin.net # private_key key from the ghp-github-app Secret, mounted as a file. GHP_GITHUB_PRIVATE_KEY_FILE: /etc/ghp/github-app/private_key # cert-manager Certificate ghp-tls, mounted from the ghp-tls Secret. diff --git a/apps/base/ghp/gateway.yaml b/apps/base/ghp/gateway.yaml index 80fb011..3407718 100644 --- a/apps/base/ghp/gateway.yaml +++ b/apps/base/ghp/gateway.yaml @@ -1,8 +1,12 @@ --- -# Management UI ingress for ghp.k8s.syd1.au.unkin.net via the internal Traefik. -# TLS is terminated with the ghp-tls Secret produced by the cert-manager -# Certificate (which also carries the GitHub SANs); no cert-manager annotation -# here so the two never fight over the same Secret. +# HTTPS front for ghp, served on two names via the internal Traefik: +# ghp.unkin.net — canonical/primary (apex, bind-operator zone) +# ghp.k8s.syd1.au.unkin.net — admin/internal route (external-dns k8s.syd1 zone) +# The cert-manager annotations below make cert-manager mint the ghp-gateway-tls +# Secret with CN ghp.unkin.net and a DNS SAN for each TLS listener hostname +# automatically. This is a SEPARATE Secret from ghp-tls (certificate.yaml), which +# carries the GitHub impersonation SANs and is mounted by ghp itself — the two +# never share a Secret, so cert-manager does not fight over either. apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: @@ -10,6 +14,12 @@ metadata: traefik.io/instance: internal annotations: argocd.argoproj.io/sync-wave: "2" + cert-manager.io/cluster-issuer: vault-issuer + cert-manager.io/common-name: ghp.unkin.net + cert-manager.io/private-key-size: "4096" + # Only the k8s admin route is published by external-dns (it owns just the + # k8s.syd1.au.unkin.net zone). ghp.unkin.net lives in the apex zone and is + # served by the bind-operator DNSRecord — NOT managed here. external-dns.alpha.kubernetes.io/hostname: ghp.k8s.syd1.au.unkin.net external-dns.alpha.kubernetes.io/target: 198.18.200.4 name: ghp @@ -17,23 +27,43 @@ metadata: spec: gatewayClassName: traefik-internal listeners: - - allowedRoutes: - namespaces: - from: Same - hostname: ghp.k8s.syd1.au.unkin.net - name: http + - name: http-primary port: 80 protocol: HTTP - - allowedRoutes: + hostname: ghp.unkin.net + allowedRoutes: namespaces: from: Same - hostname: ghp.k8s.syd1.au.unkin.net - name: https + - name: https-primary port: 443 protocol: HTTPS + hostname: ghp.unkin.net + allowedRoutes: + namespaces: + from: Same tls: + mode: Terminate certificateRefs: - group: "" kind: Secret - name: ghp-tls + name: ghp-gateway-tls + - name: http-admin + port: 80 + protocol: HTTP + hostname: ghp.k8s.syd1.au.unkin.net + allowedRoutes: + namespaces: + from: Same + - name: https-admin + port: 443 + protocol: HTTPS + hostname: ghp.k8s.syd1.au.unkin.net + allowedRoutes: + namespaces: + from: Same + tls: mode: Terminate + certificateRefs: + - group: "" + kind: Secret + name: ghp-gateway-tls diff --git a/apps/base/ghp/httproute.yaml b/apps/base/ghp/httproute.yaml index af65a04..2005f07 100644 --- a/apps/base/ghp/httproute.yaml +++ b/apps/base/ghp/httproute.yaml @@ -8,16 +8,25 @@ metadata: argocd.argoproj.io/sync-wave: "2" spec: hostnames: + - ghp.unkin.net - ghp.k8s.syd1.au.unkin.net parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: ghp - sectionName: http + sectionName: http-primary - group: gateway.networking.k8s.io kind: Gateway name: ghp - sectionName: https + sectionName: http-admin + - group: gateway.networking.k8s.io + kind: Gateway + name: ghp + sectionName: https-primary + - group: gateway.networking.k8s.io + kind: Gateway + name: ghp + sectionName: https-admin rules: - backendRefs: - group: "" From 7a1e8351a7200a16fbd6b687adb31b0b61be6e6c Mon Sep 17 00:00:00 2001 From: Unkin Agent Date: Thu, 13 Aug 2026 23:19:54 +1000 Subject: [PATCH 10/13] ghp: serve plain HTTP behind the gateway (fix redirect loop) (#361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://ghp.unkin.net/ 308-loops onto itself. The traefik gateway terminates TLS and forwards cleartext to the ghp Service port 80 -> container :8080, but :8080 was `GHP_SERVER_HTTP_LISTEN` — ghp's http->https **308 redirect** listener. So ghp bounced every request back to https, the gateway re-forwarded it to :8080, and it looped forever. ## Root cause (confirmed against ghp source) `internal/server/server.go` `Run()` picks the serving mode: ```go hasTLS := s.cfg.Server.HTTPSListen != "" || (systemd socket + certs) if hasTLS { return s.serveTLS(...) } // app on HTTPSListen (TLS); HTTPListen = 308 redirect; Listen IGNORED return s.servePlain(...) // app (full handler: mgmt UI + API) on Listen, cleartext ``` It is **strictly either/or**. `serveTLS` serves the app on `HTTPSListen` and gives `HTTPListen` only `httpsRedirectHandler()` (`redirect.go`: `http.StatusPermanentRedirect` = 308). `servePlain` serves the same full handler on `Listen` in cleartext (`createListener()` uses `cfg.Server.Listen`). The mgmt UI is the same `handler` in both modes, so it IS served on the plain `Listen` port. Behind a TLS-terminating gateway that forwards cleartext to :8080, ghp therefore has to run in **plain mode**. Keeping `GHP_SERVER_HTTPS_LISTEN` would keep `hasTLS` true, leave `GHP_SERVER_LISTEN` ignored, and nothing would serve cleartext on :8080. ## Change - **configmap**: drop `GHP_SERVER_HTTPS_LISTEN` and `GHP_SERVER_HTTP_LISTEN`; set `GHP_SERVER_LISTEN: ":8080"` so :8080 SERVES the app; add `GHP_SERVER_TRUST_PROXY_HEADERS: "true"` so ghp trusts the gateway's `X-Forwarded-*`/`Forwarded` for scheme/host (`GHP_SERVER_BASE_URL` already set). - **deployment + vmservicescrape**: the metrics server only wraps TLS when `hasTLS` is true (`Run()` gates `loadTLSConfig` on `hasTLS`); in plain mode it is cleartext, so the `/metrics` liveness/readiness probes and the VMServiceScrape switch from HTTPS/https to HTTP/http. Service, HTTPRoute and Gateway are unchanged. configmap+deployment carry the stakater reloader annotation, so pods roll on the change. ## Deviation from the brief The brief said to keep `GHP_SERVER_HTTPS_LISTEN: ":8443"`. Source shows that is incompatible with serving cleartext on :8080 (the two modes are mutually exclusive), so this drops it. The unused `GHP_TLS_CERT_FILE`/`KEY_FILE`, the `tls` volume, and containerPort 8443 are left in place (harmless) for an easy revert to TLS mode. The alternative — gateway -> Service 443 -> :8443 with a BackendTLSPolicy — is the bigger change flagged in the brief and is NOT taken here. Validated: `kustomize build apps/overlays/au-syd1/ghp` clean, kubeconform 0 invalid/0 errors, pre-commit clean. Not applied. --------- Co-authored-by: unkin-agent Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/361 Co-authored-by: Unkin Agent Co-committed-by: Unkin Agent --- apps/base/ghp/configmap.yaml | 16 ++++++++++++++-- apps/base/ghp/deployment.yaml | 10 ++++++++-- apps/base/ghp/vmservicescrape.yaml | 8 +++----- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/base/ghp/configmap.yaml b/apps/base/ghp/configmap.yaml index ca0ae45..f9c30b1 100644 --- a/apps/base/ghp/configmap.yaml +++ b/apps/base/ghp/configmap.yaml @@ -6,9 +6,21 @@ metadata: namespace: ghp data: GHP_DATABASE_DRIVER: postgres + # ghp runs in plain-HTTP reverse-proxy mode: the traefik gateway terminates TLS + # for ghp.unkin.net and forwards cleartext to container :8080, where ghp SERVES + # the full app (mgmt UI + API) via GHP_SERVER_LISTEN. + # + # Do NOT set GHP_SERVER_HTTPS_LISTEN here. Any non-empty https_listen flips ghp + # into TLS-only mode (server.go Run(): hasTLS -> serveTLS): the app is served on + # :8443 and http_listen becomes a 308 http->https REDIRECT listener, while + # GHP_SERVER_LISTEN is ignored entirely. The gateway forwarding cleartext into + # that redirect listener on :8080 was the ghp.unkin.net -> ghp.unkin.net 308 loop. + # # nonroot cannot bind <1024; listen high and remap in the Service. - GHP_SERVER_HTTPS_LISTEN: ":8443" - GHP_SERVER_HTTP_LISTEN: ":8080" + GHP_SERVER_LISTEN: ":8080" + # Behind the TLS-terminating gateway: trust its X-Forwarded-* / Forwarded headers + # for scheme/host when generating absolute URLs (GHP_SERVER_BASE_URL is also set). + GHP_SERVER_TRUST_PROXY_HEADERS: "true" GHP_METRICS_LISTEN: ":9136" GHP_METRICS_ENABLED: "true" GHP_SERVER_BASE_URL: https://ghp.unkin.net diff --git a/apps/base/ghp/deployment.yaml b/apps/base/ghp/deployment.yaml index 1a7f5b2..e4619b7 100644 --- a/apps/base/ghp/deployment.yaml +++ b/apps/base/ghp/deployment.yaml @@ -102,7 +102,10 @@ spec: httpGet: path: /metrics port: metrics - scheme: HTTPS + # Plain HTTP: ghp only serves metrics over TLS in TLS mode + # (hasTLS). In reverse-proxy/plain mode the metrics server is + # cleartext, so probe with HTTP. + scheme: HTTP initialDelaySeconds: 30 periodSeconds: 30 successThreshold: 1 @@ -112,7 +115,10 @@ spec: httpGet: path: /metrics port: metrics - scheme: HTTPS + # Plain HTTP: ghp only serves metrics over TLS in TLS mode + # (hasTLS). In reverse-proxy/plain mode the metrics server is + # cleartext, so probe with HTTP. + scheme: HTTP initialDelaySeconds: 10 periodSeconds: 5 successThreshold: 1 diff --git a/apps/base/ghp/vmservicescrape.yaml b/apps/base/ghp/vmservicescrape.yaml index e5ca485..ea6a7a8 100644 --- a/apps/base/ghp/vmservicescrape.yaml +++ b/apps/base/ghp/vmservicescrape.yaml @@ -16,8 +16,6 @@ spec: endpoints: - port: metrics path: /metrics - scheme: https - # ghp serves metrics over TLS with an internal-CA cert; skip verification - # since the scrape targets a pod IP the cert SANs do not cover. - tlsConfig: - insecureSkipVerify: true + # ghp runs in plain reverse-proxy mode (no GHP_SERVER_HTTPS_LISTEN), so the + # metrics server is cleartext HTTP rather than TLS. Scrape over http. + scheme: http From 20438520ab2c9791e3ad282b2819b1e3c384768c Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Thu, 13 Aug 2026 23:40:53 +1000 Subject: [PATCH 11/13] woodpecker: add autobackup-operator CI ServiceAccount (#362) The autobackup-operator Woodpecker pipeline needs a dedicated ServiceAccount to run its CI jobs under, matching the existing per-project operator CI SAs in the woodpecker namespace (e.g. kea-operator-ci). - Adds ServiceAccount autobackup-operator-ci in the woodpecker namespace - Registers serviceaccount_autobackup_operator_ci.yaml in the woodpecker kustomization.yaml resources list Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/362 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/woodpecker/kustomization.yaml | 1 + .../woodpecker/serviceaccount_autobackup_operator_ci.yaml | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 apps/base/woodpecker/serviceaccount_autobackup_operator_ci.yaml diff --git a/apps/base/woodpecker/kustomization.yaml b/apps/base/woodpecker/kustomization.yaml index 20564c7..baa4838 100644 --- a/apps/base/woodpecker/kustomization.yaml +++ b/apps/base/woodpecker/kustomization.yaml @@ -7,6 +7,7 @@ resources: - cnpg_cluster.yaml - cnpg_backup.yaml - cnpg_pooler.yaml + - serviceaccount_autobackup_operator_ci.yaml - serviceaccount_kea_operator_ci.yaml - serviceaccount_jellyfin_ha_src.yaml - serviceaccount_terraform_artifactapi.yaml diff --git a/apps/base/woodpecker/serviceaccount_autobackup_operator_ci.yaml b/apps/base/woodpecker/serviceaccount_autobackup_operator_ci.yaml new file mode 100644 index 0000000..46aa202 --- /dev/null +++ b/apps/base/woodpecker/serviceaccount_autobackup_operator_ci.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: autobackup-operator-ci + namespace: woodpecker From dc02b3eebd990f18419153957e86af5330a3e09e Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Fri, 14 Aug 2026 00:29:15 +1000 Subject: [PATCH 12/13] Deploy k8up backup operator (platform, k8up-system) (#363) The k8up CRDs already exist in the cluster (owned by a separate `k8up-crds` Helm release) but the k8up operator itself was never deployed, so no k8up backup/restore custom resources are reconciled. This installs the operator following the cnpg-system pattern (Helm inflated via kustomize `helmCharts`) in the `platform` ArgoCD project, namespace `k8up-system`. - Add `apps/base/k8up-system/` (Namespace + kustomization). - Add `apps/overlays/au-syd1/k8up-system/` inflating the **k8up chart `4.10.0`** (pinned) from the artifactapi virtual-helm proxy: `replicaCount: 2` (leader election with hot-standby follower), operator resource requests/limits (20m/128Mi req, 500m/256Mi lim), and ServiceMonitor/PrometheusRule disabled (require Prometheus Operator CRDs). - Add a `PodDisruptionBudget` (`minAvailable: 1`) in the overlay, since the k8up chart exposes no native PDB value. - Register `apps/overlays/*/k8up-system` in the platform ApplicationSet. **CRD management:** The k8up chart ships its CRDs via the Helm `crds/` directory (there is no `installCRDs`/`skipCrds`/`enableCRDCreation` value). The kustomize helm inflator does not emit `crds/`, so `kustomize build` renders only the operator (Deployment, RBAC, SA, Service) and no CRDs. ArgoCD therefore does not fight the existing `k8up-crds` Helm release over CRD ownership. **platform.yaml (AppProject):** no change needed. The virtual-helm sourceRepo, the `*-system` destination, and the ClusterRole/ClusterRoleBinding/CRD/webhook clusterResourceWhitelist entries are already present and cover everything this app renders. **Validation:** `kustomize build --enable-helm apps/overlays/au-syd1/k8up-system` succeeds (Deployment replicas 2, PDB, Namespace, RBAC; 0 CRDs). `make kubeconform` reports k8up-system 14/14 valid; the only failure is the pre-existing, unrelated `cattle-system` rancher chart kubeVersion issue. pre-commit passes. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/363 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/k8up-system/kustomization.yaml | 6 ++++++ apps/base/k8up-system/namespace.yaml | 5 +++++ .../au-syd1/k8up-system/kustomization.yaml | 15 +++++++++++++++ .../k8up-system/poddisruptionbudget.yaml | 12 ++++++++++++ apps/overlays/au-syd1/k8up-system/values.yaml | 18 ++++++++++++++++++ argocd/applicationsets/platform.yaml | 1 + 6 files changed, 57 insertions(+) create mode 100644 apps/base/k8up-system/kustomization.yaml create mode 100644 apps/base/k8up-system/namespace.yaml create mode 100644 apps/overlays/au-syd1/k8up-system/kustomization.yaml create mode 100644 apps/overlays/au-syd1/k8up-system/poddisruptionbudget.yaml create mode 100644 apps/overlays/au-syd1/k8up-system/values.yaml diff --git a/apps/base/k8up-system/kustomization.yaml b/apps/base/k8up-system/kustomization.yaml new file mode 100644 index 0000000..5122f9e --- /dev/null +++ b/apps/base/k8up-system/kustomization.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml diff --git a/apps/base/k8up-system/namespace.yaml b/apps/base/k8up-system/namespace.yaml new file mode 100644 index 0000000..2ede6b2 --- /dev/null +++ b/apps/base/k8up-system/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: k8up-system diff --git a/apps/overlays/au-syd1/k8up-system/kustomization.yaml b/apps/overlays/au-syd1/k8up-system/kustomization.yaml new file mode 100644 index 0000000..585cac1 --- /dev/null +++ b/apps/overlays/au-syd1/k8up-system/kustomization.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../base/k8up-system + - poddisruptionbudget.yaml + +helmCharts: + - name: k8up + repo: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/virtual/helm + version: "4.10.0" + releaseName: k8up + namespace: k8up-system + valuesFile: values.yaml diff --git a/apps/overlays/au-syd1/k8up-system/poddisruptionbudget.yaml b/apps/overlays/au-syd1/k8up-system/poddisruptionbudget.yaml new file mode 100644 index 0000000..8a9cf76 --- /dev/null +++ b/apps/overlays/au-syd1/k8up-system/poddisruptionbudget.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: k8up + namespace: k8up-system +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: k8up + app.kubernetes.io/instance: k8up diff --git a/apps/overlays/au-syd1/k8up-system/values.yaml b/apps/overlays/au-syd1/k8up-system/values.yaml new file mode 100644 index 0000000..e736888 --- /dev/null +++ b/apps/overlays/au-syd1/k8up-system/values.yaml @@ -0,0 +1,18 @@ +# CRDs are managed by the separate `k8up-crds` Helm release already present in +# the cluster; the k8up chart ships CRDs via its crds/ directory, which the +# kustomize helm inflator does not emit, so ArgoCD does not fight CRD ownership. +replicaCount: 2 + +resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 20m + memory: 128Mi + +metrics: + serviceMonitor: + enabled: false # Requires Prometheus Operator CRDs. + prometheusRule: + enabled: false # Requires Prometheus Operator CRDs. diff --git a/argocd/applicationsets/platform.yaml b/argocd/applicationsets/platform.yaml index 22f8e08..97a4c39 100644 --- a/argocd/applicationsets/platform.yaml +++ b/argocd/applicationsets/platform.yaml @@ -31,6 +31,7 @@ spec: - path: apps/overlays/*/grafana-system - path: apps/overlays/*/inteldeviceplugins-system - path: apps/overlays/*/jfrog + - path: apps/overlays/*/k8up-system - path: apps/overlays/*/kanidm - path: apps/overlays/*/netbox - path: apps/overlays/*/node-feature-discovery From 8503a4b7a4a40eaad8c0ace925854ab03b07d428 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Fri, 14 Aug 2026 00:29:34 +1000 Subject: [PATCH 13/13] woodpecker: add ghp CI ServiceAccount (#364) ## Why The ghp fork (`unkin/ghp`) now has `.woodpecker` pipelines whose steps run under `serviceAccountName: ghp`. Without this ServiceAccount in the `woodpecker` namespace, pods fail to schedule (`error looking up service account woodpecker/ghp: serviceaccount "ghp" not found`), which fails the required `ci/woodpecker/pr/build` status. ## Change - Add `apps/base/woodpecker/serviceaccount_ghp.yaml` (ServiceAccount `ghp` in namespace `woodpecker`), mirroring the existing per-project SAs (e.g. `jellyfin-ha-src`). - Register it in `apps/base/woodpecker/kustomization.yaml`. Unblocks the ghp CI onboarding for unkin/ghp#1. Reviewed-on: https://git.unkin.net/unkin/argocd-apps/pulls/364 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- apps/base/woodpecker/kustomization.yaml | 1 + apps/base/woodpecker/serviceaccount_ghp.yaml | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 apps/base/woodpecker/serviceaccount_ghp.yaml diff --git a/apps/base/woodpecker/kustomization.yaml b/apps/base/woodpecker/kustomization.yaml index baa4838..6f79eb6 100644 --- a/apps/base/woodpecker/kustomization.yaml +++ b/apps/base/woodpecker/kustomization.yaml @@ -8,6 +8,7 @@ resources: - cnpg_backup.yaml - cnpg_pooler.yaml - serviceaccount_autobackup_operator_ci.yaml + - serviceaccount_ghp.yaml - serviceaccount_kea_operator_ci.yaml - serviceaccount_jellyfin_ha_src.yaml - serviceaccount_terraform_artifactapi.yaml diff --git a/apps/base/woodpecker/serviceaccount_ghp.yaml b/apps/base/woodpecker/serviceaccount_ghp.yaml new file mode 100644 index 0000000..a3c8b3a --- /dev/null +++ b/apps/base/woodpecker/serviceaccount_ghp.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: ghp + namespace: woodpecker