diff --git a/argocd-apps/vps-standby/gitlab.yaml b/argocd-apps/vps-standby/gitlab.yaml new file mode 100644 index 0000000..dc30723 --- /dev/null +++ b/argocd-apps/vps-standby/gitlab.yaml @@ -0,0 +1,33 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: vps-gitlab + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "3" # after vps-cnpg (wave 1) and vps-eso (wave 1) + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: vps-standby + sources: + - repoURL: https://charts.gitlab.io/ + chart: gitlab + targetRevision: 7.7.0 + helm: + valueFiles: + - $values/infrastructure/vps-standby/gitlab/values.yaml + - repoURL: https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git + targetRevision: main + ref: values + - repoURL: https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git + targetRevision: main + path: infrastructure/vps-standby/gitlab/manifests + destination: + name: vps-standby + namespace: gitlab + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/infrastructure/authentik/gitlab-blueprint.yaml b/infrastructure/authentik/gitlab-blueprint.yaml index 50798f5..7e142b8 100644 --- a/infrastructure/authentik/gitlab-blueprint.yaml +++ b/infrastructure/authentik/gitlab-blueprint.yaml @@ -49,6 +49,10 @@ spec: redirect_uris: - url: "https://gitlab.kube.huskypup.net/users/auth/openid_connect/callback" matching_mode: strict + # Real GitLab on the VPS (infrastructure/vps-standby/gitlab/) - + # shares this same provider/client_id, added 2026-08-21. + - url: "https://gitlab.vps.huskypup.net/users/auth/openid_connect/callback" + matching_mode: strict enabled: true property_mappings: - !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]] diff --git a/infrastructure/vps-standby/gitlab/manifests/cnpg-cluster.yaml b/infrastructure/vps-standby/gitlab/manifests/cnpg-cluster.yaml new file mode 100644 index 0000000..ec5f3d4 --- /dev/null +++ b/infrastructure/vps-standby/gitlab/manifests/cnpg-cluster.yaml @@ -0,0 +1,54 @@ +--- +# Real GitLab on the VPS - simplified single-node equivalent of +# apps/gitlab/manifests/cnpg-cluster.yaml (home's is 3-instance HA with +# Rook-Ceph; this is 1 instance on local-path, matching the "keep it +# lighter" pattern already used for the rest of vps-standby). +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: pg-gitlab + namespace: gitlab +spec: + imageName: ghcr.io/cloudnative-pg/postgresql:16 + instances: 1 + + resources: + requests: + memory: "512Mi" + cpu: "50m" + limits: + memory: "2Gi" + cpu: "500m" + + storage: + size: 20Gi + storageClass: local-path + + primaryUpdateStrategy: unsupervised + + bootstrap: + initdb: + database: gitlabhq_production + owner: app + postInitSQL: + - CREATE EXTENSION IF NOT EXISTS pg_trgm; + - CREATE EXTENSION IF NOT EXISTS btree_gist; + + # Same tuning as home's, GitLab needs these regardless of scale + postgresql: + parameters: + max_connections: "200" + shared_buffers: "512MB" + effective_cache_size: "1536MB" + maintenance_work_mem: "128MB" + checkpoint_completion_target: "0.9" + wal_buffers: "16MB" + default_statistics_target: "100" + random_page_cost: "1.1" + effective_io_concurrency: "200" + work_mem: "8MB" + min_wal_size: "1GB" + max_wal_size: "4GB" + + monitoring: + enablePodMonitor: false # no Prometheus on the VPS cluster diff --git a/infrastructure/vps-standby/gitlab/manifests/ingress.yaml b/infrastructure/vps-standby/gitlab/manifests/ingress.yaml new file mode 100644 index 0000000..dd7a793 --- /dev/null +++ b/infrastructure/vps-standby/gitlab/manifests/ingress.yaml @@ -0,0 +1,42 @@ +--- +# Real GitLab on the VPS. https-redirect Middleware from the start this +# time - see infrastructure/vps-standby/vault/manifests/ingress.yaml for +# why it's needed (found live 2026-08-21: Traefik serves plain HTTP +# alongside HTTPS unless explicitly redirected, which silently breaks +# OIDC login since the callback URL computed client-side won't match +# what's registered in Authentik). +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: https-redirect + namespace: gitlab +spec: + redirectScheme: + scheme: https + permanent: true +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: vps-gitlab + namespace: gitlab + annotations: + cert-manager.io/cluster-issuer: letsencrypt-production + traefik.ingress.kubernetes.io/router.middlewares: gitlab-https-redirect@kubernetescrd +spec: + ingressClassName: traefik + tls: + - hosts: + - gitlab.vps.huskypup.net + secretName: vps-gitlab-tls + rules: + - host: gitlab.vps.huskypup.net + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: gitlab-webservice-default + port: + number: 8181 diff --git a/infrastructure/vps-standby/gitlab/manifests/oidc-secret.yaml b/infrastructure/vps-standby/gitlab/manifests/oidc-secret.yaml new file mode 100644 index 0000000..bd6da0d --- /dev/null +++ b/infrastructure/vps-standby/gitlab/manifests/oidc-secret.yaml @@ -0,0 +1,34 @@ +# Shares the same Authentik OAuth2 provider/client_id as home's GitLab +# (infrastructure/authentik/gitlab-blueprint.yaml has both redirect_uris +# registered). Same Vault path (secret/gitlab-oauth) home's copy reads +# from - vps-eso-reader policy widened to allow this one extra path +# (infrastructure/vps-eso/manifests/clustersecretstore.yaml's policy, +# applied live via vault CLI, not git-tracked - matches how every other +# Vault policy/auth-method in this repo is set up). +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: gitlab-oidc + namespace: gitlab +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: gitlab-oidc-secret + creationPolicy: Owner + template: + type: Opaque + data: + GITLAB_OIDC_CLIENT_ID: "{{ .clientId }}" + GITLAB_OIDC_CLIENT_SECRET: "{{ .clientSecret }}" + data: + - secretKey: clientId + remoteRef: + key: gitlab-oauth + property: client-id + - secretKey: clientSecret + remoteRef: + key: gitlab-oauth + property: client-secret diff --git a/infrastructure/vps-standby/gitlab/manifests/redis-init-fix.yaml b/infrastructure/vps-standby/gitlab/manifests/redis-init-fix.yaml new file mode 100644 index 0000000..168f6cf --- /dev/null +++ b/infrastructure/vps-standby/gitlab/manifests/redis-init-fix.yaml @@ -0,0 +1,19 @@ +# Same fix as apps/gitlab/manifests/redis-init-fix.yaml (home) - GitLab +# chart 7.7.0's initializer ordering bug: 7_prometheus_metrics.rb loads +# before 7_redis.rb (alphabetical 'p' < 'r'), triggering Redis cache +# access before Gitlab::Redis::ALL_CLASSES is defined. This ConfigMap +# provides an early initializer that loads the Redis module first. +# +# Mounting this into webservice/sidekiq deployments still has to be a +# manual kubectl patch after first deploy - chart 7.7.0 doesn't support +# extraVolumes on those workloads, same limitation home hit, and home's +# own patch was never git-tracked either (applied live). See this +# directory's own deploy notes for the exact patch commands. +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-init-fix + namespace: gitlab +data: + 0_redis_early.rb: | + require "gitlab/redis" diff --git a/infrastructure/vps-standby/gitlab/manifests/redis-secret.yaml b/infrastructure/vps-standby/gitlab/manifests/redis-secret.yaml new file mode 100644 index 0000000..715cbfb --- /dev/null +++ b/infrastructure/vps-standby/gitlab/manifests/redis-secret.yaml @@ -0,0 +1,22 @@ +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: redis-gitlab + namespace: gitlab +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: vault-backend + target: + name: redis-gitlab-secret + creationPolicy: Owner + template: + type: Opaque + data: + password: "{{ .password }}" + data: + - secretKey: password + remoteRef: + key: vps/gitlab-redis + property: password diff --git a/infrastructure/vps-standby/gitlab/manifests/redis.yaml b/infrastructure/vps-standby/gitlab/manifests/redis.yaml new file mode 100644 index 0000000..5530404 --- /dev/null +++ b/infrastructure/vps-standby/gitlab/manifests/redis.yaml @@ -0,0 +1,84 @@ +# Real GitLab on the VPS - standalone Redis, same pattern as home's +# apps/gitlab/manifests/redis-cluster.yaml (despite that file's name, +# it's already just a single standalone instance - "Sentinel" in the +# filename is legacy, not what's actually running). local-path storage +# instead of rook-ceph-block. +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-gitlab + namespace: gitlab +spec: + ports: + - port: 6379 + targetPort: 6379 + name: redis + selector: + app: redis-gitlab + type: ClusterIP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-gitlab + namespace: gitlab +spec: + serviceName: redis-gitlab + replicas: 1 + selector: + matchLabels: + app: redis-gitlab + template: + metadata: + labels: + app: redis-gitlab + spec: + securityContext: + fsGroup: 1000 + containers: + - name: redis + image: redis:7.0-alpine + securityContext: + runAsUser: 999 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + ports: + - containerPort: 6379 + name: redis + command: + - sh + - -c + - redis-server --appendonly yes --requirepass "$REDIS_PASSWORD" + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-gitlab-secret + key: password + resources: + requests: + cpu: 25m + memory: 128Mi + limits: + memory: 512Mi + volumeMounts: + - name: data + mountPath: /data + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: local-path + resources: + requests: + storage: 5Gi diff --git a/infrastructure/vps-standby/gitlab/values.yaml b/infrastructure/vps-standby/gitlab/values.yaml new file mode 100644 index 0000000..58cd94d --- /dev/null +++ b/infrastructure/vps-standby/gitlab/values.yaml @@ -0,0 +1,195 @@ +# Real GitLab on the VPS - simplified single-node equivalent of +# apps/gitlab/values.yaml (home). Same chart, same version, same +# Authentik OIDC provider/client (shared, see infrastructure/authentik/ +# gitlab-blueprint.yaml). Deliberately lighter than home's: no Praefect +# (pointless with 1 Gitaly node), no PgBouncer (direct CNPG connection, +# low enough traffic not to need pooling), no SAML, no Container +# Registry, no KAS - all addable later if actually needed, matches the +# "keep it lighter" decision already made for the rest of vps-standby. + +global: + hosts: + domain: vps.huskypup.net + gitlab: + name: gitlab.vps.huskypup.net + + # Ingress disabled - own Ingress + Traefik https-redirect Middleware + # handle routing (infrastructure/vps-standby/gitlab/manifests/ingress.yaml) + ingress: + enabled: false + configureCertmanager: false + + edition: ce + time_zone: UTC + + email: + from: 'gitlab@vps.huskypup.net' + display_name: GitLab (VPS) + reply_to: 'noreply@vps.huskypup.net' + + # External PostgreSQL via CNPG (infrastructure/vps-standby/gitlab/manifests/cnpg-cluster.yaml) + psql: + host: pg-gitlab-rw.gitlab.svc.cluster.local + port: 5432 + database: gitlabhq_production + username: app + password: + secret: pg-gitlab-app + key: password + + # Single Gitaly node, no Praefect - HA routing is pointless with 1 node + gitaly: + enabled: true + internal: + names: + - default + external: [] + + praefect: + enabled: false + + # Standalone Redis (infrastructure/vps-standby/gitlab/manifests/redis.yaml) + redis: + host: redis-gitlab.gitlab.svc.cluster.local + port: 6379 + auth: + enabled: true + secret: redis-gitlab-secret + key: password + + appConfig: + omniauth: + enabled: true + allowSingleSignOn: ['openid_connect'] + blockAutoCreatedUsers: false + autoLinkUser: ['openid_connect'] + syncProfileFromProvider: ['openid_connect'] + syncProfileAttributes: ['email', 'name'] + providers: + - secret: gitlab-oidc-secret + key: provider + +postgresql: + install: false + +redis: + install: false + +pgbouncer: + enabled: false + +# GitLab's own bundled MinIO for object storage (uploads, artifacts) - +# separate from vps-minio (which backs CNPG WAL archiving for other apps) +minio: + persistence: + storageClass: local-path + size: 20Gi + resources: + requests: + cpu: 25m + memory: 256Mi + limits: + memory: 1Gi + +registry: + enabled: false + +gitlab: + webservice: + minReplicas: 1 + maxReplicas: 1 + workerProcesses: 2 + + extraEnvFrom: + GITLAB_OIDC_CLIENT_ID: + secretKeyRef: + name: gitlab-oidc-secret + key: GITLAB_OIDC_CLIENT_ID + GITLAB_OIDC_CLIENT_SECRET: + secretKeyRef: + name: gitlab-oidc-secret + key: GITLAB_OIDC_CLIENT_SECRET + extraEnv: + GITLAB_OMNIBUS_CONFIG: | + gitlab_rails['omniauth_enabled'] = true + gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect'] + gitlab_rails['omniauth_block_auto_created_users'] = false + gitlab_rails['omniauth_auto_link_user'] = ['openid_connect'] + gitlab_rails['omniauth_sync_profile_from_provider'] = ['openid_connect'] + gitlab_rails['omniauth_sync_profile_attributes'] = ['email', 'name'] + + gitlab_rails['omniauth_providers'] = [ + { + 'name' => 'openid_connect', + 'label' => 'Authentik', + 'args' => { + 'name' => 'openid_connect', + 'scope' => ['openid', 'profile', 'email'], + 'response_type' => 'code', + 'issuer' => 'https://auth.kube.huskypup.net/application/o/gitlab/', + 'discovery' => true, + 'client_auth_method' => 'query', + 'uid_field' => 'sub', + 'send_scope_to_token_endpoint' => true, + 'pkce' => true, + 'client_options' => { + 'identifier' => ENV['GITLAB_OIDC_CLIENT_ID'], + 'secret' => ENV['GITLAB_OIDC_CLIENT_SECRET'], + 'redirect_uri' => 'https://gitlab.vps.huskypup.net/users/auth/openid_connect/callback' + } + } + } + ] + resources: + requests: + cpu: 150m + memory: 1536Mi + limits: + memory: 3Gi + + kas: + enabled: false + + gitaly: + persistence: + storageClass: local-path + size: 30Gi + resources: + requests: + cpu: 50m + memory: 256Mi + limits: + cpu: "1" + memory: 1Gi + + gitlab-runner: + install: false + + gitlab-exporter: + enabled: true + metrics: + enabled: false # no Prometheus on the VPS cluster + + sidekiq: + minReplicas: 1 + maxReplicas: 1 + resources: + requests: + cpu: 25m + memory: 384Mi + limits: + cpu: 250m + memory: 1536Mi + +certmanager: + install: false + installCRDs: false + +prometheus: + install: false + +nginx-ingress: + enabled: false + +gitlab-runner: + install: false