diff --git a/apps/base/artifactapi/api-deployment.yaml b/apps/base/artifactapi/api-deployment.yaml index 80bc87e..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.8.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 38de490..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.8.0 + image: git.unkin.net/unkin/artifactapi-ui:v3.11.1 imagePullPolicy: IfNotPresent ports: - containerPort: 80 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/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..f9c30b1 --- /dev/null +++ b/apps/base/ghp/configmap.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ghp-env + 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_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 + 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. + 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: "neoloc" diff --git a/apps/base/ghp/deployment.yaml b/apps/base/ghp/deployment.yaml new file mode 100644 index 0000000..e4619b7 --- /dev/null +++ b/apps/base/ghp/deployment.yaml @@ -0,0 +1,151 @@ +--- +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: 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. + 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 + # 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 + timeoutSeconds: 5 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: metrics + # 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 + 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..3407718 --- /dev/null +++ b/apps/base/ghp/gateway.yaml @@ -0,0 +1,69 @@ +--- +# 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: + labels: + 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 + namespace: ghp +spec: + gatewayClassName: traefik-internal + listeners: + - name: http-primary + port: 80 + protocol: HTTP + hostname: ghp.unkin.net + allowedRoutes: + namespaces: + from: Same + - name: https-primary + port: 443 + protocol: HTTPS + hostname: ghp.unkin.net + allowedRoutes: + namespaces: + from: Same + tls: + mode: Terminate + certificateRefs: + - group: "" + kind: Secret + 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 new file mode 100644 index 0000000..2005f07 --- /dev/null +++ b/apps/base/ghp/httproute.yaml @@ -0,0 +1,40 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ghp-route + namespace: ghp + annotations: + 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-primary + - group: gateway.networking.k8s.io + kind: Gateway + name: ghp + 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: "" + 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..ef8438c --- /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: ghcr.io/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..ea6a7a8 --- /dev/null +++ b/apps/base/ghp/vmservicescrape.yaml @@ -0,0 +1,21 @@ +--- +# 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 + # 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 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/base/woodpecker/kustomization.yaml b/apps/base/woodpecker/kustomization.yaml index 6977aba..6f79eb6 100644 --- a/apps/base/woodpecker/kustomization.yaml +++ b/apps/base/woodpecker/kustomization.yaml @@ -7,7 +7,10 @@ resources: - cnpg_cluster.yaml - 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 - serviceaccount_terraform_authentik.yaml - serviceaccount_terraform_enc.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 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 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 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/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 5508107..97a4c39 100644 --- a/argocd/applicationsets/platform.yaml +++ b/argocd/applicationsets/platform.yaml @@ -26,10 +26,12 @@ 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 - path: apps/overlays/*/jfrog + - path: apps/overlays/*/k8up-system - path: apps/overlays/*/kanidm - path: apps/overlays/*/netbox - path: apps/overlays/*/node-feature-discovery 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'