Initial commit

This commit is contained in:
Scooby Husky
2026-03-09 20:21:35 -05:00
commit aacb8eebbe
314 changed files with 21766 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# Environment-specific overrides
environments/local.yaml
# Temporary files
*.swp
*.swo
*~
.DS_Store
# Helm
charts/*/charts/
charts/*/tmpcharts/
# IDE
.idea/
.vscode/
+106
View File
@@ -0,0 +1,106 @@
# CLAUDE.md
This file provides guidance to Claude Code when working with this repository.
## Overview
Homelabv4 is a fully automated Kubernetes homelab deployment using **ArgoCD App-of-Apps pattern** for GitOps-based continuous delivery. It replaces the Helmfile-based Homelabv5 with declarative ArgoCD Application CRDs, sync waves for ordering, and Kubernetes Jobs for complex hooks.
## Deployment
### Bootstrap (one-time)
```bash
# Prerequisites: ArgoCD must already be running on the cluster
# Replace <GIT_REPO_URL> in all Application CRDs with your actual repo URL
# Then:
kubectl apply -f bootstrap.yaml
```
### How It Works
1. `bootstrap.yaml` creates a root Application pointing to `argocd-apps/`
2. ArgoCD discovers all Application CRDs in `argocd-apps/infrastructure/` and `argocd-apps/apps/`
3. Sync waves control deployment order (lower waves first)
4. ArgoCD waits for resources to be healthy before advancing waves
5. PreSync/PostSync hook Jobs handle complex initialization
### Verify Deployment
```bash
kubectl get applications -n argocd
kubectl get pods -A | grep -v Running | grep -v Completed
```
## Architecture
### Sync Wave Scheme
| Wave | Phase | Components |
|------|-------|-----------|
| -1 | Bootstrap | Prometheus CRDs |
| 1-7 | Core Net | Cilium, Istio, cert-manager, metrics-server, envoy-gateway |
| 10-14 | Storage | Rook-Ceph, CNPG, Redis, MariaDB, Scylla operators |
| 20-23 | Security | Vault, ESO, Reloader, Authentik, Kyverno, Tetragon, CrowdSec |
| 30-31 | Net Extra | External-DNS, Netbird, Mosquitto |
| 40-43 | Monitoring | Prometheus, Grafana, Kiali, Checkov |
| 50-52 | Apps | GitLab, ArgoCD, n8n, Nextcloud, TeslaMate, HA, Frigate, etc. |
### Directory Structure
```
Homelabv4/
├── bootstrap.yaml # Root App-of-Apps
├── argocd-apps/ # Application CRDs
│ ├── infrastructure/ # ~35 infra Applications
│ └── apps/ # ~10 user Applications
├── infrastructure/ # Source files for infra
│ └── <service>/
│ ├── values.yaml # Helm values
│ └── manifests/ # Raw K8s manifests
├── apps/ # Source files for apps
│ └── <app>/
│ ├── values.yaml
│ └── manifests/
├── charts/ # Local Helm charts
├── hooks/ # Shared hook RBAC
├── environments/ # Environment config
└── scripts/ # Manual scripts
```
### Key Patterns
#### Multi-Source Applications
Most Applications use 3 sources:
1. Remote Helm chart repo
2. Git repo ref for values files
3. Git repo path for raw manifests
#### Hook Jobs
Complex initialization (Vault init, OAuth sync) uses K8s Jobs with ArgoCD hook annotations:
- `argocd.argoproj.io/hook: PreSync` or `PostSync`
- `argocd.argoproj.io/hook-delete-policy: BeforeHookCreation`
- Jobs use `argocd-hook-sa` ServiceAccount with cluster-admin
#### Secrets Flow
Vault → External Secrets Operator → Kubernetes Secrets → Apps
## Common Operations
### Add a New Application
1. Create `apps/<name>/values.yaml` with Helm values
2. Create `apps/<name>/manifests/` with any raw manifests
3. Create `argocd-apps/apps/<name>.yaml` Application CRD
4. Commit and push — ArgoCD auto-syncs
### Update Helm Values
1. Edit `infrastructure/<service>/values.yaml` or `apps/<app>/values.yaml`
2. Commit and push — ArgoCD auto-syncs
### Force Sync
```bash
argocd app sync <app-name>
```
## Important Notes
- **Git repo URL**: Replace `<GIT_REPO_URL>` placeholder in all Application CRDs
- **Secrets**: Never commit secrets — use Vault + ExternalSecret
- **Domain**: All services use `*.kube.huskypup.net`
- **Storage**: Default StorageClass is `rook-ceph-block`
- **Ingress**: Istio VirtualServices + Envoy Gateway HTTPRoutes
- **mTLS**: Istio ambient mesh with STRICT PeerAuthentication
@@ -0,0 +1,84 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm-custom-health
namespace: argocd
labels:
app.kubernetes.io/part-of: argocd
annotations:
argocd.argoproj.io/sync-wave: "-1"
data:
resource.customizations.health.ceph.rook.io_CephCluster: |
hs = {}
if obj.status ~= nil then
if obj.status.phase == "Ready" and obj.status.ceph ~= nil and obj.status.ceph.health == "HEALTH_OK" then
hs.status = "Healthy"
hs.message = "CephCluster is healthy"
elseif obj.status.phase == "Progressing" then
hs.status = "Progressing"
hs.message = obj.status.message or "CephCluster is progressing"
else
hs.status = "Degraded"
hs.message = obj.status.message or "CephCluster is not healthy"
end
else
hs.status = "Progressing"
hs.message = "Waiting for CephCluster status"
end
return hs
resource.customizations.health.postgresql.cnpg.io_Cluster: |
hs = {}
if obj.status ~= nil then
if obj.status.phase == "Cluster in healthy state" then
hs.status = "Healthy"
hs.message = "CNPG Cluster is healthy"
elseif obj.status.phase == "Setting up primary" or obj.status.phase == "Creating primary" then
hs.status = "Progressing"
hs.message = obj.status.phase
else
hs.status = "Degraded"
hs.message = obj.status.phase or "CNPG Cluster is not healthy"
end
else
hs.status = "Progressing"
hs.message = "Waiting for CNPG Cluster status"
end
return hs
resource.customizations.health.external-secrets.io_ClusterSecretStore: |
hs = {}
if obj.status ~= nil and obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "Ready" then
if condition.status == "True" then
hs.status = "Healthy"
hs.message = "ClusterSecretStore is ready"
else
hs.status = "Degraded"
hs.message = condition.message or "ClusterSecretStore is not ready"
end
return hs
end
end
end
hs.status = "Progressing"
hs.message = "Waiting for ClusterSecretStore status"
return hs
resource.customizations.health.kiali.io_Kiali: |
hs = {}
if obj.status ~= nil then
if obj.status.conditions ~= nil then
for i, condition in ipairs(obj.status.conditions) do
if condition.type == "Successful" and condition.status == "True" then
hs.status = "Healthy"
hs.message = "Kiali is healthy"
return hs
end
end
end
hs.status = "Progressing"
hs.message = "Kiali is being deployed"
else
hs.status = "Progressing"
hs.message = "Waiting for Kiali status"
end
return hs
+20
View File
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: argocd
namespace: argocd
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- argocd.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: argocd-server
port: 80
@@ -0,0 +1,23 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: argocd-oauth
namespace: argocd
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: argocd-oauth-secret
creationPolicy: Owner
data:
- secretKey: client-id
remoteRef:
key: argocd-oauth
property: client-id
- secretKey: client-secret
remoteRef:
key: argocd-oauth
property: client-secret
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: argocd
namespace: argocd
spec:
hosts:
- argocd.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: argocd-server.argocd.svc.cluster.local
port:
number: 80
+78
View File
@@ -0,0 +1,78 @@
# values/argocd.values.yaml
global:
domain: argocd.kube.huskypup.net
configs:
cm:
url: https://argocd.kube.huskypup.net
oidc.config: |
name: Authentik
issuer: https://auth.kube.huskypup.net/application/o/argocd/
clientID: $oidc.authentik.clientId
clientSecret: $oidc.authentik.clientSecret
requestedScopes:
- openid
- profile
- email
- groups
rbac:
policy.default: role:readonly
policy.csv: |
g, Authentik Admins, role:admin
g, ArgoCD Admins, role:admin
server:
extraArgs:
- --insecure # TLS is terminated at Istio gateway
ingress:
enabled: false
env:
- name: oidc.authentik.clientId
valueFrom:
secretKeyRef:
name: argocd-oauth-secret
key: client-id
- name: oidc.authentik.clientSecret
valueFrom:
secretKeyRef:
name: argocd-oauth-secret
key: client-secret
dex:
enabled: false # using Authentik instead of built-in Dex
# Enable Prometheus metrics
metrics:
enabled: true
serviceMonitor:
enabled: true
additionalLabels:
release: prometheus
controller:
metrics:
enabled: true
serviceMonitor:
enabled: true
additionalLabels:
release: prometheus
repoServer:
metrics:
enabled: true
serviceMonitor:
enabled: true
additionalLabels:
release: prometheus
applicationSet:
metrics:
enabled: true
serviceMonitor:
enabled: true
additionalLabels:
release: prometheus
+20
View File
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: frigate
namespace: frigate
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- frigate.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: frigate
port: 5000
@@ -0,0 +1,27 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: frigate
namespace: frigate
spec:
hosts:
- frigate.kube.huskypup.net
gateways:
- istio-system/edge
http:
# Authentik forward-auth endpoints must be reachable on the protected host
- match:
- uri:
prefix: /outpost.goauthentik.io
route:
- destination:
host: authentik-server.authentik.svc.cluster.local
port:
number: 80
- timeout: 3600s
route:
- destination:
host: frigate.frigate.svc.cluster.local
port:
number: 5000
+29
View File
@@ -0,0 +1,29 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: frigate-config
namespace: frigate
labels:
app.kubernetes.io/name: frigate
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block
resources:
requests:
storage: 500Mi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: frigate-media
namespace: frigate
labels:
app.kubernetes.io/name: frigate
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block
resources:
requests:
storage: 500Gi
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
name: frigate-rtsp-credentials
namespace: frigate
type: Opaque
stringData:
# Update these with your actual camera credentials
# Then update apps/frigate/values.yaml with your camera RTSP URLs
FRIGATE_RTSP_USERNAME: your_camera_username
FRIGATE_RTSP_PASSWORD: your_camera_password
+182
View File
@@ -0,0 +1,182 @@
# Frigate Helm Chart Values
# NVR with realtime object detection for IP cameras
#
# FEATURES:
# ✓ Object detection for IP cameras
# ✓ Persistent storage for recordings via Rook-Ceph
# ✓ TLS certificates via cert-manager
# ✓ Ingress via Istio VirtualService
# ✓ MQTT integration (optional)
# Reduce Istio sidecar CPU request - frigate already uses 1 CPU request
# and the cluster runs at ~98% CPU allocation. Default 10m sidecar request
# can prevent scheduling.
podAnnotations:
sidecar.istio.io/proxyCPU: "1m"
image:
repository: ghcr.io/blakeblackshear/frigate
tag: "0.13.2"
pullPolicy: IfNotPresent
service:
main:
type: ClusterIP
ports:
http:
port: 5000
# Ingress disabled - Istio VirtualService handles routing
ingress:
main:
enabled: false
env:
TZ: America/New_York
FRIGATE_MQTT_HOST: "mosquitto.mqtt"
FRIGATE_MQTT_PORT: "1883"
envFromSecrets:
- frigate-rtsp-credentials
config: |
mqtt:
host: mosquitto.mqtt
port: 1883
topic_prefix: frigate
client_id: frigate
detectors:
cpu1:
type: cpu
cameras:
basement:
ffmpeg:
inputs:
- path: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=1
roles:
- detect
- path: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=0
roles:
- record
detect:
width: 704
height: 480
fps: 5
record:
enabled: true
retain:
days: 5
mode: all
snapshots:
enabled: true
timestamp: true
bounding_box: true
patio:
ffmpeg:
inputs:
- path: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=1
roles:
- detect
- path: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=0
roles:
- record
detect:
width: 704
height: 480
fps: 5
record:
enabled: true
retain:
days: 5
mode: all
snapshots:
enabled: true
timestamp: true
bounding_box: true
backyard:
ffmpeg:
inputs:
- path: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=1
roles:
- detect
- path: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=0
roles:
- record
detect:
width: 704
height: 480
fps: 5
record:
enabled: true
retain:
days: 5
mode: all
snapshots:
enabled: true
timestamp: true
bounding_box: true
go2rtc:
streams:
basement: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=0
basement_alt: rtsp://admin:admin!@172.28.200.10:554/cam/realmonitor?channel=1&subtype=1
patio: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=0
patio_alt: rtsp://admin:admin!@172.28.200.11:554/cam/realmonitor?channel=1&subtype=1
backyard: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=0
backyard_alt: rtsp://admin:admin!@172.28.200.12:554/cam/realmonitor?channel=1&subtype=1
objects:
track:
- person
- car
- dog
- cat
- vehicle
motion:
threshold: 25
persistence:
config:
enabled: true
storageClass: rook-ceph-block
accessMode: ReadWriteOnce
size: 500Mi
media:
enabled: true
storageClass: rook-ceph-block
accessMode: ReadWriteOnce
size: 500Gi
shmSize: 2Gi
tmpfs:
enabled: true
sizeLimit: 2Gi
securityContext:
privileged: false
capabilities:
drop:
- ALL
resources:
requests:
cpu: 50m
memory: 1Gi
limits:
cpu: 500m
memory: 4Gi
probes:
liveness:
enabled: true
initialDelaySeconds: 5
failureThreshold: 5
readiness:
enabled: true
initialDelaySeconds: 5
failureThreshold: 5
startup:
enabled: true
failureThreshold: 5
nodeSelector:
kubernetes.io/arch: amd64
+74
View File
@@ -0,0 +1,74 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-gitlab
namespace: gitlab
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16
instances: 3 # 3 instances for production HA
# Database resources (homelab-friendly requests, burstable CPU)
resources:
requests:
memory: "1Gi"
cpu: "50m"
limits:
memory: "4Gi"
cpu: "500m"
# Spread replicas across different nodes
affinity:
topologyKey: kubernetes.io/hostname
storage:
size: 50Gi # Increased for production
storageClass: rook-ceph-block
primaryUpdateStrategy: unsupervised
bootstrap:
initdb:
database: gitlabhq_production
owner: app
postInitSQL:
- CREATE EXTENSION IF NOT EXISTS pg_trgm;
- CREATE EXTENSION IF NOT EXISTS btree_gist;
# PostgreSQL configuration tuning for GitLab
postgresql:
parameters:
max_connections: "400"
shared_buffers: "1GB"
effective_cache_size: "3GB"
maintenance_work_mem: "256MB"
checkpoint_completion_target: "0.9"
wal_buffers: "16MB"
default_statistics_target: "100"
random_page_cost: "1.1"
effective_io_concurrency: "200"
work_mem: "16MB"
min_wal_size: "1GB"
max_wal_size: "4GB"
max_worker_processes: "4"
max_parallel_workers_per_gather: "2"
max_parallel_workers: "4"
# Backup configuration to MinIO
backup:
barmanObjectStore:
destinationPath: s3://gitlab-backups/pg-gitlab
endpointURL: http://gitlab-minio-svc.gitlab.svc.cluster.local:9000
s3Credentials:
accessKeyId:
name: gitlab-minio-secret
key: accesskey
secretAccessKey:
name: gitlab-minio-secret
key: secretkey
wal:
compression: gzip
maxParallel: 2
retentionPolicy: "30d"
monitoring:
enablePodMonitor: true
+86
View File
@@ -0,0 +1,86 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitlab-web
namespace: gitlab
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- gitlab.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: gitlab-webservice-default
port: 8181
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitlab-registry
namespace: gitlab
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- registry.gitlab.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: gitlab-registry
port: 5000
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitlab-minio
namespace: gitlab
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- minio.gitlab.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: gitlab-minio-svc
port: 9000
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitlab-kas
namespace: gitlab
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- kas.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: gitlab-kas
port: 8154
@@ -0,0 +1,27 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: gitlab-saml
namespace: gitlab
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: gitlab-saml-secret
creationPolicy: Owner
template:
type: Opaque
data:
GITLAB_SAML_IDP_SSO_URL: "{{ .idp_sso_url }}"
GITLAB_SAML_IDP_FINGERPRINT: "{{ .idp_fingerprint }}"
data:
- secretKey: idp_sso_url
remoteRef:
key: gitlab/saml
property: idp_sso_url
- secretKey: idp_fingerprint
remoteRef:
key: gitlab/saml
property: idp_fingerprint
@@ -0,0 +1,19 @@
# GitLab Unified TLS Certificate
# Covers all GitLab domains in a single certificate
# This prevents issues with GitLab chart creating separate certificates
# that may have incorrect domain names
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gitlab-unified-tls
namespace: gitlab
spec:
secretName: gitlab-tls
issuerRef:
name: letsencrypt-production
kind: ClusterIssuer
dnsNames:
- gitlab.kube.huskypup.net
- registry.gitlab.kube.huskypup.net
- minio.gitlab.kube.huskypup.net
- kas.kube.huskypup.net
@@ -0,0 +1,74 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: gitlab
namespace: gitlab
spec:
hosts:
- gitlab.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: gitlab-webservice-default.gitlab.svc.cluster.local
port:
number: 8181
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: gitlab-registry
namespace: gitlab
spec:
hosts:
- registry.gitlab.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: gitlab-registry.gitlab.svc.cluster.local
port:
number: 5000
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: gitlab-minio
namespace: gitlab
spec:
hosts:
- minio.gitlab.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: gitlab-minio-svc.gitlab.svc.cluster.local
port:
number: 9000
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: gitlab-kas
namespace: gitlab
spec:
hosts:
- kas.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: gitlab-kas.gitlab.svc.cluster.local
port:
number: 8154
+206
View File
@@ -0,0 +1,206 @@
---
# ServiceAccount for the CronJob that monitors PostgreSQL secret changes
apiVersion: v1
kind: ServiceAccount
metadata:
name: pg-restart-sa
namespace: gitlab
---
# Role to allow patching Deployments, StatefulSets, Clusters and reading Secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pg-restart-role
namespace: gitlab
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "patch"]
- apiGroups: ["postgresql.cnpg.io"]
resources: ["clusters"]
verbs: ["get", "patch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["deployments/status", "statefulsets/status"]
verbs: ["get"]
- apiGroups: ["postgresql.cnpg.io"]
resources: ["clusters/status"]
verbs: ["get"]
---
# RoleBinding to grant permissions to the ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pg-restart-binding
namespace: gitlab
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pg-restart-role
subjects:
- kind: ServiceAccount
name: pg-restart-sa
namespace: gitlab
---
# CronJob to monitor pg-gitlab-app secret and trigger restarts on changes
apiVersion: batch/v1
kind: CronJob
metadata:
name: pg-gitlab-secret-monitor
namespace: gitlab
spec:
# Run every 30 minutes to check for secret changes (rotations happen at most daily)
schedule: "*/30 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
metadata:
labels:
app: pg-gitlab-secret-monitor
spec:
serviceAccountName: pg-restart-sa
restartPolicy: OnFailure
containers:
- name: monitor
image: docker.io/alpine/k8s:1.32.13
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
command:
- /bin/bash
- -c
- |
set -e
# Get current secret version
SECRET_VERSION=$(kubectl get secret -n gitlab pg-gitlab-app -o jsonpath='{.metadata.resourceVersion}')
# Get last known secret version from pgbouncer deployment annotation
LAST_VERSION=$(kubectl get deployment -n gitlab pgbouncer-gitlab -o jsonpath='{.spec.template.metadata.annotations.secret-version/pg-password}' 2>/dev/null || echo "")
echo "Current secret version: $SECRET_VERSION"
echo "Last known version: $LAST_VERSION"
# If versions differ, update database password and restart resources
if [ "$SECRET_VERSION" != "$LAST_VERSION" ]; then
echo "Secret has changed! Updating database password and resources..."
# Get the new password from the secret
NEW_PASSWORD=$(kubectl get secret -n gitlab pg-gitlab-app -o jsonpath='{.data.password}' | base64 -d)
# Update the database user password
# Try both pg-gitlab-1 and pg-gitlab-2 in case one is restarting
kubectl exec -n gitlab pg-gitlab-1 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
kubectl exec -n gitlab pg-gitlab-2 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
echo "Database password update failed"
# Update password table with new hash for PgBouncer SCRAM auth
kubectl exec -n gitlab pg-gitlab-1 -c postgres -- psql -U postgres -d gitlabhq_production -c "INSERT INTO public.user_passwords (usename, passwd) SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'app' ON CONFLICT (usename) DO UPDATE SET passwd = EXCLUDED.passwd;" 2>/dev/null || \
kubectl exec -n gitlab pg-gitlab-2 -c postgres -- psql -U postgres -d gitlabhq_production -c "INSERT INTO public.user_passwords (usename, passwd) SELECT rolname, rolpassword FROM pg_authid WHERE rolname = 'app' ON CONFLICT (usename) DO UPDATE SET passwd = EXCLUDED.passwd;" 2>/dev/null || \
echo "Password table update failed, PgBouncer may need manual restart"
# Patch pgbouncer deployments to trigger restart
kubectl patch deployment -n gitlab pgbouncer-gitlab -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}" 2>/dev/null || echo "Deployment patch failed"
echo "Database password updated and resources will restart."
else
echo "Secret has not changed. No restart needed."
fi
volumes:
- name: tmp
emptyDir: {}
---
# CronJob to monitor pg-praefect-app secret and trigger restarts on changes
apiVersion: batch/v1
kind: CronJob
metadata:
name: pg-praefect-secret-monitor
namespace: gitlab
spec:
# Run every 30 minutes to check for secret changes (rotations happen at most daily)
schedule: "*/30 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
metadata:
labels:
app: pg-praefect-secret-monitor
spec:
serviceAccountName: pg-restart-sa
restartPolicy: OnFailure
containers:
- name: monitor
image: docker.io/alpine/k8s:1.32.13
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
command:
- /bin/bash
- -c
- |
set -e
# Get current secret version
SECRET_VERSION=$(kubectl get secret -n gitlab pg-praefect-app -o jsonpath='{.metadata.resourceVersion}')
# Get last known secret version from gitaly statefulset annotation
LAST_VERSION=$(kubectl get statefulset -n gitlab gitlab-gitaly-default -o jsonpath='{.spec.template.metadata.annotations.secret-version/pg-password}' 2>/dev/null || echo "")
echo "Current secret version: $SECRET_VERSION"
echo "Last known version: $LAST_VERSION"
# If versions differ, update database password and restart resources
if [ "$SECRET_VERSION" != "$LAST_VERSION" ]; then
echo "Secret has changed! Updating database password and resources..."
# Get the new password from the secret
NEW_PASSWORD=$(kubectl get secret -n gitlab pg-praefect-app -o jsonpath='{.data.password}' | base64 -d)
# Update the database user password
# Try both pg-praefect-3 and pg-praefect-4
kubectl exec -n gitlab pg-praefect-3 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
kubectl exec -n gitlab pg-praefect-4 -c postgres -- psql -U postgres -d gitlabhq_production -c "ALTER USER app PASSWORD '$NEW_PASSWORD';" 2>/dev/null || \
echo "Database password update failed"
# Patch gitaly and praefect statefulsets
kubectl patch statefulset -n gitlab gitlab-gitaly-default -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}"
kubectl patch statefulset -n gitlab gitlab-praefect -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}"
# Patch CNPG cluster
kubectl patch cluster -n gitlab pg-praefect -p "{\"metadata\":{\"annotations\":{\"secret-version/pg-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}" --type merge
echo "Database password updated and resources will restart."
else
echo "Secret has not changed. No restart needed."
fi
volumes:
- name: tmp
emptyDir: {}
+116
View File
@@ -0,0 +1,116 @@
---
# PgBouncer Pooler for GitLab PostgreSQL
# Managed by CloudNativePG Operator
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pgbouncer-gitlab
namespace: gitlab
spec:
cluster:
name: pg-gitlab
# Number of PgBouncer instances (keep small; GitLab points directly at CNPG RW service)
instances: 1
# PgBouncer configuration
type: rw # Read-Write pooler (connects to primary)
pgbouncer:
poolMode: transaction
authQuerySecret:
name: pg-gitlab-app
# Use custom user_search function for SCRAM-SHA-256 authentication
# This function is created by gitlab-bootstrap.sh script
authQuery: "SELECT usename, passwd FROM public.user_search($1)"
parameters:
max_client_conn: "2000"
default_pool_size: "50"
reserve_pool_size: "10"
server_idle_timeout: "600" # Keep connections alive for 10 minutes
log_connections: "1"
log_disconnections: "1"
log_pooler_errors: "1"
stats_period: "60"
# Template for PgBouncer pods
template:
metadata:
labels:
app: pgbouncer-gitlab
spec:
containers:
- name: pgbouncer
resources:
requests:
cpu: 25m
memory: 256Mi
limits:
memory: 512Mi
# Anti-affinity to spread PgBouncer pods across nodes
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: pgbouncer-gitlab
topologyKey: kubernetes.io/hostname
---
# Read-only Pooler for Database Load Balancing
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: pgbouncer-gitlab-ro
namespace: gitlab
spec:
cluster:
name: pg-gitlab
instances: 3
type: ro # Read-Only pooler (connects to replicas)
pgbouncer:
poolMode: transaction
authQuerySecret:
name: pg-gitlab-app
# Use custom user_search function for SCRAM-SHA-256 authentication
# This function is created by gitlab-bootstrap.sh script
authQuery: "SELECT usename, passwd FROM public.user_search($1)"
parameters:
max_client_conn: "2000"
default_pool_size: "50"
reserve_pool_size: "10"
max_db_connections: "100"
server_idle_timeout: "600" # Keep connections alive for 10 minutes
log_connections: "1"
log_disconnections: "1"
template:
metadata:
labels:
app: pgbouncer-gitlab-ro
spec:
containers:
- name: pgbouncer
resources:
requests:
cpu: 25m
memory: 256Mi
limits:
memory: 512Mi
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: pgbouncer-gitlab-ro
topologyKey: kubernetes.io/hostname
@@ -0,0 +1,35 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-praefect
namespace: gitlab
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16
instances: 2
# Resources: keep memory, keep CPU requests low for scheduling.
# CPU limits omitted so Postgres can burst when available.
resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "2Gi"
# Spread replicas across different nodes
affinity:
topologyKey: kubernetes.io/hostname
storage:
size: 10Gi
storageClass: rook-ceph-block
primaryUpdateStrategy: unsupervised
bootstrap:
initdb:
database: praefect_production
owner: app
monitoring:
enablePodMonitor: true
@@ -0,0 +1,108 @@
---
# ServiceAccount for the CronJob that monitors Redis secret changes
apiVersion: v1
kind: ServiceAccount
metadata:
name: redis-restart-sa
namespace: gitlab
---
# Role to allow patching StatefulSets and reading Secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: redis-restart-role
namespace: gitlab
rules:
- apiGroups: ["apps"]
resources: ["statefulsets"]
verbs: ["get", "patch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
- apiGroups: ["apps"]
resources: ["statefulsets/status"]
verbs: ["get"]
---
# RoleBinding to grant permissions to the ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: redis-restart-binding
namespace: gitlab
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: redis-restart-role
subjects:
- kind: ServiceAccount
name: redis-restart-sa
namespace: gitlab
---
# CronJob to monitor Redis secret and trigger StatefulSet restart on changes
apiVersion: batch/v1
kind: CronJob
metadata:
name: redis-secret-monitor
namespace: gitlab
spec:
# Run every hour to check for secret changes
# This aligns with the 24-hour secret rotation schedule
schedule: "*/60 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
metadata:
labels:
app: redis-secret-monitor
spec:
serviceAccountName: redis-restart-sa
restartPolicy: OnFailure
containers:
- name: monitor
image: docker.io/alpine/k8s:1.32.13
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
command:
- /bin/bash
- -c
- |
set -e
# Get current secret version
SECRET_VERSION=$(kubectl get secret -n gitlab redis-gitlab-secret -o jsonpath='{.metadata.resourceVersion}')
# Get last known secret version from StatefulSet annotation
LAST_VERSION=$(kubectl get statefulset -n gitlab redis-gitlab -o jsonpath='{.spec.template.metadata.annotations.secret-version/redis-password}' 2>/dev/null || echo "")
echo "Current secret version: $SECRET_VERSION"
echo "Last known version: $LAST_VERSION"
# If versions differ, restart StatefulSet
if [ "$SECRET_VERSION" != "$LAST_VERSION" ]; then
echo "Secret has changed! Updating StatefulSet with new version annotation..."
# Patch StatefulSet with new secret version annotation
# This will trigger a rolling restart of the Redis pod
kubectl patch statefulset -n gitlab redis-gitlab -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"secret-version/redis-password\":\"$SECRET_VERSION\",\"restarted-at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}}}}}"
echo "StatefulSet will now perform a rolling restart to pick up the new password."
else
echo "Secret has not changed. No restart needed."
fi
volumes:
- name: tmp
emptyDir: {}
+95
View File
@@ -0,0 +1,95 @@
# Redis standalone instance for GitLab (used instead of Sentinel for simplicity)
# Password auth is required - GitLab reads the password from redis-gitlab-secret
---
apiVersion: v1
kind: Service
metadata:
name: redis-gitlab-additional
namespace: gitlab
spec:
ports:
- port: 6379
targetPort: 6379
name: redis
selector:
app: redis-gitlab-standalone
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: redis-gitlab-standalone
namespace: gitlab
spec:
ports:
- port: 6379
targetPort: 6379
name: redis
selector:
app: redis-gitlab-standalone
type: ClusterIP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-gitlab-standalone
namespace: gitlab
spec:
serviceName: redis-gitlab-additional
replicas: 1
selector:
matchLabels:
app: redis-gitlab-standalone
template:
metadata:
labels:
app: redis-gitlab-standalone
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: 256Mi
limits:
memory: 512Mi
volumeMounts:
- name: data
mountPath: /data
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: rook-ceph-block
resources:
requests:
storage: 5Gi
@@ -0,0 +1,33 @@
---
# Password generator and ESO for GitLab Redis
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: gitlab-redis-password
namespace: gitlab
spec:
length: 32
digits: 5
symbols: 0
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: gitlab-redis-password
namespace: gitlab
spec:
refreshInterval: "0" # Generate once, never rotate (Password generator creates new value each refresh)
target:
name: redis-gitlab-secret
creationPolicy: Owner
template:
data:
password: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: gitlab-redis-password
+114
View File
@@ -0,0 +1,114 @@
---
# Job to sync GitLab admin status from Authentik groups
# Run this after users login via Authentik SSO to grant them admin access
apiVersion: batch/v1
kind: Job
metadata:
name: gitlab-sync-admin
namespace: gitlab
spec:
ttlSecondsAfterFinished: 3600 # Clean up after 1 hour
template:
spec:
restartPolicy: OnFailure
containers:
- name: sync-admin
image: docker.io/library/alpine:3.21
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
command:
- /bin/sh
- -c
- |
set -e
apk add --no-cache postgresql-client curl
echo "🔄 Syncing GitLab admin permissions from Authentik..."
# Get list of users in "authentik Admins" group
ADMIN_USERS=$(PGPASSWORD="$AUTHENTIK_DB_PASSWORD" psql -h pg-authentik-rw.authentik.svc.cluster.local -U app -d app -t -c "
SELECT DISTINCT u.email
FROM authentik_core_user u
JOIN authentik_core_user_groups ug ON u.id = ug.user_id
JOIN authentik_core_group g ON ug.group_id = g.group_uuid
WHERE g.name = 'authentik Admins' AND u.is_active = true;
" | xargs)
if [ -z "$ADMIN_USERS" ]; then
echo "⚠️ No users found in 'authentik Admins' group"
exit 0
fi
echo "✓ Found admin users: $ADMIN_USERS"
echo ""
# For each admin user, grant admin access in GitLab
for email in $ADMIN_USERS; do
echo "🔐 Checking user: $email"
# Use GitLab Rails runner to promote user
kubectl exec -n gitlab deployment/gitlab-toolbox -- \
gitlab-rails runner "
user = User.find_by(email: '$email')
if user
if user.admin?
puts ' ✓ Already admin'
else
user.update(admin: true)
puts ' ✅ Promoted to admin'
end
else
puts ' ⚠️ User not found (needs to login via SSO first)'
end
" || echo " ❌ Failed to update user"
done
echo ""
echo "✅ Admin sync complete"
env:
- name: AUTHENTIK_DB_PASSWORD
valueFrom:
secretKeyRef:
name: pg-authentik-app
namespace: authentik
key: password
serviceAccountName: gitlab-sync-admin
---
# ServiceAccount for the sync job
apiVersion: v1
kind: ServiceAccount
metadata:
name: gitlab-sync-admin
namespace: gitlab
---
# Role to allow exec into toolbox pod
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: gitlab-sync-admin
namespace: gitlab
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["get", "list", "create"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: gitlab-sync-admin
namespace: gitlab
subjects:
- kind: ServiceAccount
name: gitlab-sync-admin
namespace: gitlab
roleRef:
kind: Role
name: gitlab-sync-admin
apiGroup: rbac.authorization.k8s.io
+333
View File
@@ -0,0 +1,333 @@
# values/gitlab.values.yaml
# GitLab with Authentik OIDC SSO Integration
global:
hosts:
domain: kube.huskypup.net
gitlab:
name: gitlab.kube.huskypup.net
registry:
name: registry.gitlab.kube.huskypup.net
tls:
secretName: gitlab-tls
minio:
name: minio.gitlab.kube.huskypup.net
tls:
secretName: gitlab-tls
kas:
name: kas.kube.huskypup.net
tls:
secretName: gitlab-tls
# Ingress disabled - Istio VirtualServices handle routing
ingress:
enabled: false
configureCertmanager: false # Use cluster-wide cert-manager, not GitLab's
# Edition: Community Edition
edition: ce
# Time zone
time_zone: UTC
# Email configuration (configure as needed)
email:
from: 'gitlab@kube.huskypup.net'
display_name: GitLab
reply_to: 'noreply@kube.huskypup.net'
# External PostgreSQL configuration
# NOTE: PgBouncer service currently has no endpoints (replicas=0),
# so GitLab is pointed directly at the CNPG primary service.
psql:
host: pg-gitlab-rw.gitlab.svc.cluster.local
port: 5432
database: gitlabhq_production
username: app
password:
secret: pg-gitlab-app
key: password
# Gitaly configuration - using Praefect for HA
gitaly:
enabled: true # Enabled to deploy Gitaly pods
internal:
names: [] # No internal Gitaly, using Praefect
external: [] # Praefect configured below
# Praefect configuration
praefect:
enabled: true
# Use CNPG database secret
dbSecret:
secret: pg-praefect-app
key: password
virtualStorages:
- name: default
gitalyReplicas: 3 # Production HA
maxUnavailable: 1
# Praefect PostgreSQL configuration
psql:
host: pg-praefect-rw.gitlab.svc.cluster.local
port: 5432
dbName: praefect_production
user: app
# External Redis configuration - using standalone Redis for writes
# (replicated Redis service causes READONLY errors from replicas)
redis:
host: redis-gitlab-standalone.gitlab.svc.cluster.local
port: 6379
auth:
enabled: true
secret: redis-gitlab-secret
key: password
# Application Configuration
appConfig:
# OmniAuth SSO Configuration
omniauth:
enabled: true
allowSingleSignOn: ['openid_connect']
blockAutoCreatedUsers: false
autoLinkUser: ['openid_connect']
syncProfileFromProvider: ['openid_connect']
syncProfileAttributes: ['email', 'name']
providers:
- secret: gitlab-oidc-secret
key: provider
# Settings for Let's Encrypt ACME Issuer - disabled, using cluster-wide cert-manager
certmanager-issuer:
email: admin@kube.huskypup.net
# Authentik OIDC Configuration via Rails omnibus config
# Note: Credentials are loaded from gitlab-oidc-secret via environment variables
# SAML Configuration via Rails omnibus config
# Note: SAML provider configuration is loaded from gitlab-saml-secret
# PostgreSQL (external via CloudNativePG)
postgresql:
install: false
# Redis (external)
redis:
install: false
# PgBouncer connection pooler
# NOTE: PgBouncer is deployed via CNPG Pooler CRD (see pgbouncer-pooler.yaml)
# This setting disables GitLab's bundled PgBouncer chart
pgbouncer:
enabled: false # Using external CNPG Pooler instead
# MinIO for object storage
minio:
persistence:
storageClass: rook-ceph-block
size: 100Gi # Production storage
resources:
requests:
cpu: 100m
memory: 512Mi
limits:
memory: 2Gi
# Container Registry - enabled with S3 storage
registry:
enabled: true
hpa:
minReplicas: 1
maxReplicas: 2
resources:
requests:
cpu: 25m
memory: 128Mi
limits:
memory: 1Gi
# GitLab components
gitlab:
# GitLab Webservice - Main application
webservice:
minReplicas: 1 # Homelab sizing
maxReplicas: 3
# Note: hostAliases for OIDC SSL validation are applied via helmfile postsync hook
# (GitLab chart doesn't support hostAliases in values.yaml)
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
GITLAB_SAML_IDP_FINGERPRINT:
secretKeyRef:
name: gitlab-saml-secret
key: GITLAB_SAML_IDP_FINGERPRINT
GITLAB_SAML_IDP_SSO_URL:
secretKeyRef:
name: gitlab-saml-secret
key: GITLAB_SAML_IDP_SSO_URL
extraEnv:
GITLAB_OMNIBUS_CONFIG: |
# Authentik OIDC Configuration
gitlab_rails['omniauth_enabled'] = true
gitlab_rails['omniauth_allow_single_sign_on'] = ['openid_connect', 'saml']
gitlab_rails['omniauth_block_auto_created_users'] = false
gitlab_rails['omniauth_auto_link_user'] = ['openid_connect', 'saml']
gitlab_rails['omniauth_auto_sign_in_with_provider'] = nil
gitlab_rails['omniauth_sync_profile_from_provider'] = ['openid_connect', 'saml']
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.kube.huskypup.net/users/auth/openid_connect/callback'
}
}
},
{
'name' => 'saml',
'label' => 'Authentik SAML',
'args' => {
'assertion_consumer_service_url' => 'https://gitlab.kube.huskypup.net/users/auth/saml/callback',
'idp_cert_fingerprint' => ENV['GITLAB_SAML_IDP_FINGERPRINT'],
'idp_sso_target_url' => ENV['GITLAB_SAML_IDP_SSO_URL'],
'issuer' => 'https://gitlab.kube.huskypup.net',
'name_identifier_format' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
'attribute_statements' => {
'email' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'],
'name' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'],
'first_name' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'],
'last_name' => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname']
}
}
}
]
# Homelab resource allocation
resources:
requests:
cpu: 50m
memory: 512Mi
limits:
cpu: 500m
memory: 2Gi
# GitLab KAS (Kubernetes Agent Server) - enabled for Kubernetes cluster integration
kas:
enabled: true
minReplicas: 1
maxReplicas: 1
resources:
requests:
cpu: 25m
memory: 256Mi
limits:
cpu: 500m
memory: 1Gi
# Gitaly Cluster (Praefect) - High Availability Git storage
gitaly:
# Note: enabled is in global.gitaly
persistence:
storageClass: rook-ceph-block
size: 200Gi # Production storage
# Production resources
resources:
requests:
cpu: 50m
memory: 512Mi
limits:
cpu: 250m
memory: 2Gi
# GitLab Runner - enabled for CI/CD
gitlab-runner:
install: false # Will be installed separately
# Praefect - Gitaly Cluster routing and transaction manager
praefect:
enabled: true
minReplicas: 1 # Homelab sizing
maxReplicas: 1
# Note: Praefect PostgreSQL config is in global.praefect.psql
# Use CNPG-generated database secret
dbSecret:
secret: pg-praefect-app
key: password
# Resources
resources:
requests:
cpu: 25m
memory: 256Mi
limits:
cpu: 250m
memory: 512Mi
# Virtual storage configuration
virtualStorages:
- name: default
gitalyReplicas: 1 # Homelab sizing
maxUnavailable: 1
# GitLab Exporter for Prometheus metrics
gitlab-exporter:
enabled: true
metrics:
enabled: true
# Sidekiq background jobs
sidekiq:
minReplicas: 1 # Homelab sizing
maxReplicas: 1
# Note: hostAliases applied via helmfile postsync hook
# Homelab resources
resources:
requests:
cpu: 25m
memory: 512Mi
limits:
cpu: 250m
memory: 2Gi
# Disable components we already have in the cluster
certmanager:
install: false # Using cluster-wide cert-manager
installCRDs: false
prometheus:
install: false # Using existing Prometheus
nginx-ingress:
enabled: false # Istio handles ingress
# Disable GitLab Runner (configure separately if needed)
gitlab-runner:
install: false
@@ -0,0 +1,50 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-guacamole
namespace: guacamole
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16
instances: 2
# Resource limits to prevent OOM
resources:
requests:
memory: "256Mi"
cpu: "50m"
limits:
memory: "1Gi"
# Spread replicas across different nodes
affinity:
topologyKey: kubernetes.io/hostname
storage:
size: 10Gi
storageClass: rook-ceph-block
primaryUpdateStrategy: unsupervised
# PostgreSQL configuration for Guacamole
postgresql:
parameters:
max_connections: "100"
shared_buffers: "256MB"
effective_cache_size: "768MB"
maintenance_work_mem: "64MB"
checkpoint_completion_target: "0.9"
wal_buffers: "8MB"
default_statistics_target: "100"
random_page_cost: "1.1"
effective_io_concurrency: "200"
work_mem: "1310kB"
min_wal_size: "1GB"
max_wal_size: "4GB"
bootstrap:
initdb:
database: guacamole
owner: guacamole
monitoring:
enablePodMonitor: true
@@ -0,0 +1,114 @@
# Job to initialize Guacamole database schema
# This should run once after the PostgreSQL cluster is ready
apiVersion: batch/v1
kind: Job
metadata:
name: guacamole-init-schema
namespace: guacamole
spec:
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: OnFailure
initContainers:
- name: wait-for-postgres
image: postgres:16-alpine
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
command:
- sh
- -c
- |
echo "Waiting for PostgreSQL to be ready..."
until pg_isready -h pg-guacamole-rw -p 5432 -U guacamole; do
echo "PostgreSQL not ready, waiting..."
sleep 5
done
echo "PostgreSQL is ready!"
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: pg-guacamole-app
key: password
- name: generate-initdb
image: guacamole/guacamole:1.6.0
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
command:
- sh
- -c
- |
set -e
/opt/guacamole/bin/initdb.sh --postgresql > /initdb/initdb.sql
test -s /initdb/initdb.sql
volumeMounts:
- name: initdb
mountPath: /initdb
containers:
- name: init-schema
image: postgres:16-alpine
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
command:
- sh
- -c
- |
set -e
echo "Checking if schema already exists..."
TABLES=$(PGPASSWORD="$PGPASSWORD" psql -h pg-guacamole-rw -U guacamole -d guacamole -tA -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'guacamole_user';" 2>/dev/null | tr -d '[:space:]' || printf '0')
TABLES=${TABLES:-0}
if [ "$TABLES" -gt "0" ]; then
echo "Schema already exists."
else
echo "Initializing Guacamole database schema..."
PGPASSWORD="$PGPASSWORD" psql -v ON_ERROR_STOP=1 -h pg-guacamole-rw -U guacamole -d guacamole -f /initdb/initdb.sql
echo "Schema initialization complete!"
fi
echo "Setting up Authentik Admins group permissions..."
PGPASSWORD="$PGPASSWORD" psql -v ON_ERROR_STOP=1 -h pg-guacamole-rw -U guacamole -d guacamole -f /schema/permissions.sql
echo "Authentik Admins group permissions configured!"
env:
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: pg-guacamole-app
key: password
volumeMounts:
- name: initdb
mountPath: /initdb
- name: schema
mountPath: /schema
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
- name: initdb
emptyDir: {}
- name: schema
configMap:
name: guacamole-schema
@@ -0,0 +1,256 @@
# Guacamole complete deployment with OpenID/Authentik support
# This deployment includes:
# - Environment-based OpenID configuration
# - PostgreSQL backend for connections/users
# - Authentik uses Let's Encrypt (no custom cert import needed)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacamole
namespace: guacamole
labels:
app: guacamole
component: client
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
selector:
matchLabels:
app: guacamole
component: client
template:
metadata:
labels:
app: guacamole
component: client
spec:
automountServiceAccountToken: false
containers:
- name: guacamole
image: guacamole/guacamole:1.6.0
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: tomcat-work
mountPath: /usr/local/tomcat/work
- name: tomcat-logs
mountPath: /usr/local/tomcat/logs
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: GUACD_HOSTNAME
value: "guacd"
- name: GUACD_PORT
value: "4822"
- name: POSTGRESQL_HOSTNAME
value: "pg-guacamole-rw"
- name: POSTGRESQL_PORT
value: "5432"
- name: POSTGRESQL_DATABASE
value: "guacamole"
- name: POSTGRESQL_ENABLED
value: "true"
- name: POSTGRESQL_USERNAME
value: "guacamole"
- name: POSTGRESQL_PASSWORD
valueFrom:
secretKeyRef:
name: pg-guacamole-app
key: password
- name: POSTGRESQL_AUTO_CREATE_ACCOUNTS
value: "true"
- name: ENABLE_ENVIRONMENT_PROPERTIES
value: "true"
- name: LOG_LEVEL
value: "debug"
- name: WEBAPP_CONTEXT
value: "ROOT"
# FIX: Enabled WebSockets to stop the 10-second tunnel timeout
- name: ENABLE_WEBSOCKET
value: "true"
- name: EXTENSION_PRIORITY
value: "*,openid"
- name: OPENID_RESPONSE_TYPE
value: "code"
- name: OPENID_AUTHORIZATION_ENDPOINT
value: "https://auth.kube.huskypup.net/application/o/authorize/"
- name: OPENID_JWKS_ENDPOINT
value: "https://auth.kube.huskypup.net/application/o/guacamole/jwks/"
- name: OPENID_ISSUER
value: "https://auth.kube.huskypup.net/application/o/guacamole/"
- name: OPENID_TOKEN_ENDPOINT
value: "https://auth.kube.huskypup.net/application/o/token/"
- name: OPENID_REDIRECT_URI
value: "https://guacamole.kube.huskypup.net/"
- name: OPENID_USERNAME_CLAIM_TYPE
value: "preferred_username"
- name: OPENID_GROUPS_CLAIM_TYPE
value: "groups"
- name: OPENID_SCOPE
value: "openid email profile groups"
- name: OPENID_ALLOWED_CLOCK_SKEW
value: "30"
- name: OPENID_MAX_TOKEN_VALIDITY
value: "300"
- name: OPENID_MAX_NONCE_VALIDITY
value: "60"
- name: OPENID_CLIENT_ID
valueFrom:
secretKeyRef:
name: guacamole-oauth-secret
key: client-id
- name: OPENID_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: guacamole-oauth-secret
key: client-secret
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /api/languages
port: http
initialDelaySeconds: 60
periodSeconds: 30
# FIX: Relaxed readiness probe so minor DB lags don't kill the Endpoint
readinessProbe:
httpGet:
path: /api/languages
port: http
initialDelaySeconds: 30
periodSeconds: 15
failureThreshold: 5
volumes:
- name: tmp
emptyDir: {}
- name: tomcat-work
emptyDir: {}
- name: tomcat-logs
emptyDir: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: guacd
namespace: guacamole
labels:
app: guacamole
component: guacd
spec:
replicas: 1
selector:
matchLabels:
app: guacamole
component: guacd
template:
metadata:
labels:
app: guacamole
component: guacd
spec:
automountServiceAccountToken: false
containers:
- name: guacd
image: guacamole/guacd:1.6.0
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
- name: home
mountPath: /home
ports:
- name: guacd
containerPort: 4822
protocol: TCP
env:
- name: LOG_LEVEL
value: "debug"
resources:
requests:
memory: "256Mi"
cpu: "10m"
limits:
memory: "512Mi"
cpu: "500m"
readinessProbe:
tcpSocket:
port: 4822
initialDelaySeconds: 2
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
volumes:
- name: tmp
emptyDir: {}
- name: home
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: guacamole
namespace: guacamole
labels:
app: guacamole
component: client
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: http
protocol: TCP
name: http
selector:
app: guacamole
component: client
---
apiVersion: v1
kind: Service
metadata:
name: guacd
namespace: guacamole
labels:
app: guacamole
component: guacd
spec:
type: ClusterIP
ports:
- port: 4822
targetPort: guacd
protocol: TCP
name: guacd
selector:
app: guacamole
component: guacd
@@ -0,0 +1,67 @@
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: guacamole-envoy-tls
namespace: guacamole
spec:
secretName: guacamole-envoy-tls
issuerRef:
kind: ClusterIssuer
name: letsencrypt-production
dnsNames:
- guacamole.kube.huskypup.net
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: guacamole
namespace: guacamole
spec:
gatewayClassName: envoy-gateway
listeners:
- name: https
hostname: guacamole.kube.huskypup.net
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: guacamole-envoy-tls
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: guacamole
namespace: guacamole
spec:
parentRefs:
- name: guacamole
sectionName: https
hostnames:
- guacamole.kube.huskypup.net
rules:
# Legacy path support: https://host/guacamole/* -> https://host/*
- matches:
- path:
type: PathPrefix
value: /guacamole
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: guacamole
port: 8080
# Standard root routing
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: guacamole
port: 8080
+33
View File
@@ -0,0 +1,33 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: guacamole
namespace: guacamole
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- guacamole.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /guacamole
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: guacamole
port: 8080
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: guacamole
port: 8080
@@ -0,0 +1,29 @@
# kubernetes/apps/guacamole/external-secret.yaml
# ExternalSecrets for Guacamole - pulls credentials from Vault
# Note: Database password is managed by CNPG cluster (pg-guacamole-app secret)
# We reference it directly in the guacamole values.yaml
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: guacamole-oauth
namespace: guacamole
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: guacamole-oauth-secret
creationPolicy: Owner
data:
- secretKey: client-id
remoteRef:
key: guacamole-oauth
property: client-id
- secretKey: client-secret
remoteRef:
key: guacamole-oauth
property: client-secret
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: guacamole
namespace: guacamole
spec:
hosts:
- guacamole.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: guacamole.guacamole.svc.cluster.local
port:
number: 8080
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: esphome
namespace: home-assistant
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- esphome.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: esphome
port: 6052
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: esphome
namespace: home-assistant
spec:
hosts:
- esphome.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: esphome.home-assistant.svc.cluster.local
port:
number: 6052
+57
View File
@@ -0,0 +1,57 @@
# ESPHome Helm Chart Values
# Deployed in home-assistant namespace as a companion to Home Assistant
#
# FEATURES:
# ✓ ESPHome Dashboard for managing ESP devices
# ✓ Persistent storage via Rook-Ceph
# ✓ TLS certificates via cert-manager
# ✓ Ingress via Istio VirtualService
image:
repository: ghcr.io/esphome/esphome
tag: "2024.11.3"
pullPolicy: IfNotPresent
service:
main:
ports:
http:
port: 6052
# Ingress disabled - Istio VirtualService handles routing
ingress:
main:
enabled: false
persistence:
config:
enabled: true
storageClass: rook-ceph-block
accessMode: ReadWriteOnce
size: 5Gi
retain: true
env:
TZ: America/New_York
ESPHOME_DASHBOARD_USE_PING: "true"
securityContext:
privileged: false
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
memory: 512Mi
probes:
liveness:
enabled: true
readiness:
enabled: true
startup:
enabled: true
nodeSelector:
kubernetes.io/arch: amd64
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: home-assistant
namespace: home-assistant
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- homeassistant.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: home-assistant
port: 8123
@@ -0,0 +1,31 @@
---
# Home Assistant OIDC Credentials - External Secret
#
# This ExternalSecret syncs Home Assistant OIDC credentials from Vault
# Credentials are stored in Vault by the sync-homeassistant-oauth.sh script
# after Authentik blueprint creates the OAuth provider
#
# The secret is used by Home Assistant's hass-openid integration
# to authenticate users via Authentik OIDC
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: homeassistant-oauth
namespace: home-assistant
spec:
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: homeassistant-oidc-secret
creationPolicy: Owner
refreshInterval: 1h
data:
- secretKey: client_id
remoteRef:
key: secret/homeassistant-oauth
property: client-id
- secretKey: client_secret
remoteRef:
key: secret/homeassistant-oauth
property: client-secret
@@ -0,0 +1,17 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: home-assistant
namespace: home-assistant
spec:
hosts:
- homeassistant.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: home-assistant.home-assistant.svc.cluster.local
port:
number: 8123
@@ -0,0 +1,24 @@
---
# Home Assistant OIDC Configuration ConfigMap
#
# This ConfigMap contains the OIDC configuration snippet that gets appended
# to Home Assistant's configuration.yaml file during bootstrap
#
# The configuration enables hass-openid integration for Authentik OIDC auth
apiVersion: v1
kind: ConfigMap
metadata:
name: homeassistant-oidc-config
namespace: home-assistant
data:
oidc.yaml: |
# OIDC Authentication via Authentik
# Users authenticate via Authentik and are auto-created in Home Assistant
openid:
client_id: !secret oidc_client_id
client_secret: !secret oidc_client_secret
configure_url: "https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration"
scope: "openid profile email"
username_field: "preferred_username"
create_user: true
block_login: false
+152
View File
@@ -0,0 +1,152 @@
# Disable Istio sidecar on home-assistant pod because the init container
# (install-hass-openid) needs unrestricted network access to git clone.
# Istio CNI redirects traffic to the proxy port during init, but the proxy
# isn't running yet, causing TLS/connection failures.
# ESPHome (same namespace) still gets sidecar injection normally.
podAnnotations:
sidecar.istio.io/inject: "false"
image:
repository: ghcr.io/home-assistant/home-assistant
tag: "2025.1.2"
pullPolicy: IfNotPresent
initContainers:
install-hass-openid:
image: docker.io/alpine/git:2.47.2
command:
- /bin/sh
- -c
- |
set -e
apk add --no-cache git
# Install hass-openid custom integration
mkdir -p /config/custom_components /config/includes
cd /tmp
git clone --depth 1 https://github.com/cavefire/hass-openid.git
cp -r hass-openid/custom_components/openid /config/custom_components/
rm -rf hass-openid
# Write MQTT addons config
printf 'mqtt:\n broker: mqtt.kube.huskypup.net\n port: 1883\n username: homeassistant\n password: YOUR_MQTT_PASSWORD' > /config/includes/ha-addons.yaml
# Write OIDC credentials to secrets.yaml (from mounted k8s secret)
if [ -f /oidc-secret/client_id ] && [ -f /oidc-secret/client_secret ]; then
CLIENT_ID=$(cat /oidc-secret/client_id)
CLIENT_SECRET=$(cat /oidc-secret/client_secret)
# Preserve existing secrets, update OIDC entries
if [ -f /config/secrets.yaml ]; then
sed -i '/^oidc_client_id:/d' /config/secrets.yaml
sed -i '/^oidc_client_secret:/d' /config/secrets.yaml
fi
echo "oidc_client_id: \"${CLIENT_ID}\"" >> /config/secrets.yaml
echo "oidc_client_secret: \"${CLIENT_SECRET}\"" >> /config/secrets.yaml
echo "OIDC credentials written to secrets.yaml"
else
echo "WARNING: OIDC secret not mounted, skipping secrets.yaml"
fi
# Ensure OIDC config is in configuration.yaml
if [ ! -f /config/configuration.yaml ]; then
echo "Creating configuration.yaml with OIDC config..."
cat > /config/configuration.yaml <<'OIDCEOF'
# Home Assistant Configuration
default_config:
# OIDC Authentication via Authentik
openid:
client_id: !secret oidc_client_id
client_secret: !secret oidc_client_secret
configure_url: "https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration"
scope: "openid profile email"
username_field: "preferred_username"
create_user: true
block_login: false
OIDCEOF
elif ! grep -q "^openid:" /config/configuration.yaml; then
echo "Appending OIDC config to existing configuration.yaml..."
cat >> /config/configuration.yaml <<'OIDCEOF'
# OIDC Authentication via Authentik
openid:
client_id: !secret oidc_client_id
client_secret: !secret oidc_client_secret
configure_url: "https://auth.kube.huskypup.net/application/o/home-assistant/.well-known/openid-configuration"
scope: "openid profile email"
username_field: "preferred_username"
create_user: true
block_login: false
OIDCEOF
else
echo "OIDC config already present in configuration.yaml"
fi
volumeMounts:
- name: config
mountPath: /config
- name: oidc-secret
mountPath: /oidc-secret
readOnly: true
env:
TZ: America/New_York
service:
main:
ports:
http:
port: 8123
# Ingress disabled - Istio VirtualService handles routing
ingress:
main:
enabled: false
persistence:
config:
enabled: true
storageClass: rook-ceph-block
accessMode: ReadWriteOnce
size: 10Gi
retain: true
oidc-secret:
enabled: true
type: secret
name: homeassistant-oidc-secret
mountPath: /oidc-secret
readOnly: true
securityContext:
privileged: false
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
resources:
requests:
cpu: 50m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
probes:
liveness:
enabled: true
readiness:
enabled: true
startup:
enabled: true
mariadb:
enabled: false
postgresql:
enabled: false
influxdb:
enabled: false
metrics:
enabled: false
+51
View File
@@ -0,0 +1,51 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-n8n
namespace: n8n
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16
instances: 2
# Resource limits to prevent OOM
resources:
requests:
memory: "512Mi"
cpu: "25m"
limits:
memory: "2Gi"
cpu: "250m"
# Spread replicas across different nodes
affinity:
topologyKey: kubernetes.io/hostname
storage:
size: 10Gi
storageClass: rook-ceph-block
primaryUpdateStrategy: unsupervised
# PostgreSQL configuration for better performance
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: "2621kB"
min_wal_size: "1GB"
max_wal_size: "4GB"
bootstrap:
initdb:
database: n8n
owner: n8n
monitoring:
enablePodMonitor: true
+40
View File
@@ -0,0 +1,40 @@
---
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: n8n-cnpg-secret
namespace: n8n
spec:
length: 42
digits: 5
symbols: 5
symbolCharacters: "-_$@"
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: n8n-cnpg-secret
namespace: n8n
spec:
# Rotate database password every 24 hours
refreshInterval: "24h"
target:
# This will merge the generated password into the existing pg-n8n-app secret
name: pg-n8n-app
creationPolicy: Merge
template:
metadata:
labels:
cnpg.io/reload: "true"
data:
# Override the password field with our ESO-generated password
password: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: n8n-cnpg-secret
+20
View File
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: n8n
namespace: n8n
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- n8n.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: n8n
port: 80
+49
View File
@@ -0,0 +1,49 @@
# kubernetes/apps/n8n/external-secret.yaml
# ExternalSecrets for n8n - pulls credentials from Vault
# Note: Database password is managed by CNPG cluster (pg-n8n-app secret)
# We reference it directly in the n8n values.yaml extraEnv section
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: n8n-config
namespace: n8n
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: n8n-config-secret
creationPolicy: Owner
data:
- secretKey: encryption-key
remoteRef:
key: n8n-config
property: encryption-key
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: n8n-oauth
namespace: n8n
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: n8n-oauth-secret
creationPolicy: Owner
data:
- secretKey: client-id
remoteRef:
key: n8n-oauth
property: client-id
- secretKey: client-secret
remoteRef:
key: n8n-oauth
property: client-secret
+81
View File
@@ -0,0 +1,81 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: n8n-hooks
namespace: n8n
data:
hooks.js: |
// n8n v2.0.3 compatible hooks for Authentik forward auth integration
const { resolve, dirname } = require('path');
module.exports = {
credentials: {
create: [],
delete: [],
update: []
},
workflow: {
create: [],
delete: [],
update: []
},
server: {
started: [
async function (app) {
console.log('[n8n-hooks] Initializing forward auth middleware');
// Get Express app
const expressApp = app?.app;
if (!expressApp) {
console.error('[n8n-hooks] Express app not available');
return;
}
let issueCookie, UserRepository, Container;
try {
const n8nPath = dirname(require.resolve('n8n'));
issueCookie = require(resolve(n8nPath, 'dist/auth/jwt')).issueCookie;
UserRepository = require(resolve(n8nPath, 'dist/databases/repositories/user.repository')).UserRepository;
Container = require('typedi').Container;
} catch (error) {
console.error('[n8n-hooks] Failed to load dependencies:', error.message);
return;
}
const ignoreAuthRegexp = /^\/(assets|healthz|webhook|rest\/oauth2-credential|rest\/settings|static|icons|types)/;
// Add middleware for forward auth
expressApp.use(async (req, res, next) => {
try {
if (ignoreAuthRegexp.test(req.url)) return next();
if (req.cookies?.['n8n-auth']) return next();
if (!process.env.N8N_FORWARD_AUTH_HEADER) return next();
const headerName = process.env.N8N_FORWARD_AUTH_HEADER.toLowerCase().replace(/_/g, '-');
const email = req.headers[headerName];
if (!email) return next();
const userRepo = Container.get(UserRepository);
const user = await userRepo.findOne({ where: { email } });
if (!user) {
console.warn(`[n8n-hooks] User not found: ${email}`);
res.statusCode = 401;
res.end(`User ${email} not found. Please contact an admin.`);
return;
}
console.log(`[n8n-hooks] Auto-login: ${email}`);
issueCookie(res, user);
next();
} catch (error) {
console.error('[n8n-hooks] Middleware error:', error.message);
next();
}
});
console.log('[n8n-hooks] Forward auth middleware active');
}
]
}
};
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: n8n
namespace: n8n
spec:
hosts:
- n8n.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: n8n.n8n.svc.cluster.local
port:
number: 80
+161
View File
@@ -0,0 +1,161 @@
# kubernetes/apps/n8n/values.yaml
# n8n workflow automation platform
# Using chart: https://riatlas.github.io/chart__n8n
# --- n8n image ---
image:
repository: n8nio/n8n
tag: "2.0.3"
# --- n8n encryption key (loaded from secret) ---
n8n:
encryption_key: # Will be set via extraEnv from n8n-config-secret
# --- n8n configuration ---
config:
database:
type: postgresdb
postgresdb:
host: pg-n8n-rw # CNPG read-write service
port: 5432
database: n8n
user: n8n
schema: public
generic:
timezone: America/New_York
path: /
host: n8n.kube.huskypup.net
port: 5678
protocol: https
executions:
mode: regular
saveDataOnError: all
saveDataOnSuccess: all
saveDataManualExecutions: true
pruneData: true
pruneDataMaxAge: 3760 # 1 year in hours
# --- Secret values (passwords, etc.) ---
# Note: password is injected via extraEnvSecrets below
secret: {}
# --- Deployment replicas ---
# Increased to 2 for high availability and faster response times
replicaCount: 2
# --- Service configuration ---
service:
type: ClusterIP
port: 80
# --- Security context ---
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
# --- Ingress disabled - Istio VirtualService handles routing ---
ingress:
enabled: false
# --- Persistence for workflows and data ---
persistence:
enabled: true
type: existing
existingClaim: n8n-main-persistence
storageClass: rook-ceph-block
accessModes:
- ReadWriteOnce
size: 10Gi
# --- Resources ---
# No CPU limits (burst allowed). Keep requests minimal for scheduling.
resources:
requests:
cpu: 100m
memory: 512Mi
# --- Startup probe ---
# Allows app to start without being killed by liveness probe
startupProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 12 # 60 seconds total startup time
# --- Readiness probe ---
readinessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
# --- Liveness probe ---
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# --- Extra environment variables (from secrets) ---
# Secrets are loaded from Kubernetes secrets
extraEnvSecrets:
# Database password from CNPG-managed secret
DB_POSTGRESDB_PASSWORD:
name: pg-n8n-app
key: password
# n8n encryption key
N8N_ENCRYPTION_KEY:
name: n8n-config-secret
key: encryption-key
# --- Extra environment variables (plain values) ---
extraEnv:
# Webhook & Editor URLs
WEBHOOK_URL: https://n8n.kube.huskypup.net/
N8N_EDITOR_BASE_URL: https://n8n.kube.huskypup.net
# Logging
N8N_LOG_LEVEL: error
# --- Disable built-in PostgreSQL (we'll deploy it separately with Bitnami) ---
postgresql:
enabled: false
# --- Disable Redis (not needed for single instance) ---
redis:
enabled: false
# --- Scaling (disabled for single instance) ---
scaling:
enabled: false
# --- Extra volumes ---
extraVolumes: []
# --- Extra volume mounts ---
extraVolumeMounts: []
# --- Node selector ---
nodeSelector: {}
# --- Tolerations ---
tolerations: []
# --- Affinity ---
affinity: {}
+52
View File
@@ -0,0 +1,52 @@
# CloudNative PostgreSQL cluster for Nessus
# Low resource configuration for memory-constrained environments
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-nessus
namespace: nessus
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16
instances: 2
# Low resource limits for constrained environments
resources:
requests:
memory: "512Mi"
cpu: "50m"
limits:
memory: "1Gi"
# Spread replicas across different nodes for HA
affinity:
topologyKey: kubernetes.io/hostname
storage:
size: 20Gi
storageClass: rook-ceph-block
primaryUpdateStrategy: unsupervised
# PostgreSQL configuration optimized for low memory
postgresql:
parameters:
max_connections: "100"
shared_buffers: "256MB"
effective_cache_size: "768MB"
maintenance_work_mem: "64MB"
checkpoint_completion_target: "0.9"
wal_buffers: "8MB"
default_statistics_target: "100"
random_page_cost: "1.1"
effective_io_concurrency: "200"
work_mem: "2621kB"
min_wal_size: "512MB"
max_wal_size: "2GB"
bootstrap:
initdb:
database: nessus
owner: nessus
monitoring:
enablePodMonitor: true
+44
View File
@@ -0,0 +1,44 @@
# External Secrets Operator configuration for Nessus PostgreSQL password rotation
# This auto-generates and rotates the database password in Vault
---
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: nessus-db-password
namespace: nessus
spec:
length: 32
digits: 10
symbols: 0 # No special chars to avoid escaping issues
symbolCharacters: ""
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: nessus-cnpg-secret
namespace: nessus
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: nessus-db-secret
creationPolicy: Owner
template:
engineVersion: v2
data:
password: "{{ .password }}"
username: "nessus"
host: "pg-nessus-rw"
port: "5432"
database: "nessus"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: nessus-db-password
+134
View File
@@ -0,0 +1,134 @@
# Nessus Vulnerability Scanner Deployment
# Optimized for low resource usage in constrained environments
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nessus
namespace: nessus
labels:
app: nessus
app.kubernetes.io/name: nessus
app.kubernetes.io/component: security-scanner
spec:
replicas: 1
strategy:
type: Recreate # Only one instance can mount the PVC at a time
selector:
matchLabels:
app: nessus
template:
metadata:
labels:
app: nessus
annotations:
# Reloader auto-restart when secrets change
secret.reloader.stakater.com/reload: "nessus-admin-credentials"
spec:
containers:
- name: nessus
image: docker.io/tenable/nessus:10.8.3-ubuntu
imagePullPolicy: IfNotPresent
# Low resource configuration for constrained environments
resources:
requests:
memory: "1Gi"
cpu: "50m"
limits:
memory: "2Gi"
ports:
- name: https
containerPort: 8834
protocol: TCP
# Environment variables for Nessus configuration
env:
# Admin credentials from Vault (via ESO)
- name: USERNAME
valueFrom:
secretKeyRef:
name: nessus-admin-credentials
key: NESSUS_USERNAME
- name: PASSWORD
valueFrom:
secretKeyRef:
name: nessus-admin-credentials
key: NESSUS_PASSWORD
# Auto-update settings (plugins only to save bandwidth/time)
- name: AUTO_UPDATE
value: "plugins"
# Volume mounts for persistent data
volumeMounts:
- name: nessus-data
mountPath: /opt/nessus/var/nessus
# Startup probe - give Nessus time to initialize
startupProbe:
httpGet:
path: /
port: 8834
scheme: HTTPS
initialDelaySeconds: 60
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 60 # 15 minutes total startup time
# Liveness probe
livenessProbe:
httpGet:
path: /
port: 8834
scheme: HTTPS
initialDelaySeconds: 300
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 5
# Readiness probe
readinessProbe:
httpGet:
path: /
port: 8834
scheme: HTTPS
initialDelaySeconds: 120
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 5
# Security context
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: false # Nessus requires root
capabilities:
drop:
- ALL
add:
- NET_ADMIN # Required for network scanning
- NET_RAW # Required for raw socket scanning
volumes:
- name: nessus-data
persistentVolumeClaim:
claimName: nessus-data
---
apiVersion: v1
kind: Service
metadata:
name: nessus
namespace: nessus
labels:
app: nessus
spec:
type: ClusterIP
ports:
- name: https
port: 8834
targetPort: 8834
protocol: TCP
selector:
app: nessus
@@ -0,0 +1,30 @@
# External Secrets Operator configuration for Nessus admin credentials
# Pulls Nessus admin username and password from Vault
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: nessus-admin-secret
namespace: nessus
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: nessus-admin-credentials
creationPolicy: Owner
template:
engineVersion: v2
data:
NESSUS_USERNAME: "{{ .username }}"
NESSUS_PASSWORD: "{{ .password }}"
data:
- secretKey: username
remoteRef:
key: secret/nessus
property: admin-username
- secretKey: password
remoteRef:
key: secret/nessus
property: admin-password
@@ -0,0 +1,31 @@
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: nessus
namespace: nessus
spec:
host: nessus.nessus.svc.cluster.local
trafficPolicy:
tls:
mode: SIMPLE
sni: nessus.nessus.svc.cluster.local
insecureSkipVerify: true
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nessus
namespace: nessus
spec:
hosts:
- nessus.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: nessus.nessus.svc.cluster.local
port:
number: 8834
+13
View File
@@ -0,0 +1,13 @@
# PersistentVolumeClaim for Nessus scan data and plugin storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nessus-data
namespace: nessus
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block
resources:
requests:
storage: 50Gi
+20
View File
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: nextcloud
namespace: nextcloud
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- nextcloud.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: nextcloud
port: 8080
@@ -0,0 +1,26 @@
# kubernetes/apps/nextcloud/external-secret.yaml
# ExternalSecrets for Nextcloud - pulls OAuth credentials from Vault
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: nextcloud-oauth
namespace: nextcloud
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: nextcloud-oauth-secret
creationPolicy: Owner
data:
- secretKey: client-id
remoteRef:
key: nextcloud-oauth
property: client-id
- secretKey: client-secret
remoteRef:
key: nextcloud-oauth
property: client-secret
@@ -0,0 +1,17 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: nextcloud
namespace: nextcloud
spec:
hosts:
- nextcloud.kube.huskypup.net
gateways:
- istio-system/edge
http:
- timeout: 3600s
route:
- destination:
host: nextcloud.nextcloud.svc.cluster.local
port:
number: 8080
@@ -0,0 +1,92 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mariadb-nextcloud
namespace: nextcloud
labels:
app: mariadb-nextcloud
spec:
replicas: 1
selector:
matchLabels:
app: mariadb-nextcloud
template:
metadata:
labels:
app: mariadb-nextcloud
spec:
securityContext:
runAsUser: 999
runAsGroup: 999
fsGroup: 999
containers:
- name: mariadb
image: mariadb:11.4
securityContext:
runAsUser: 999
runAsGroup: 999
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
args:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --init-connect=SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci
env:
- name: MARIADB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mariadb-nextcloud
key: root-password
- name: MARIADB_PASSWORD
valueFrom:
secretKeyRef:
name: mariadb-nextcloud
key: password
- name: MARIADB_USER
value: nextcloud
- name: MARIADB_DATABASE
value: nextcloud
ports:
- containerPort: 3306
volumeMounts:
- name: mariadb-data
mountPath: /var/lib/mysql
- name: tmp
mountPath: /tmp
- name: run-mysqld
mountPath: /run/mysqld
volumes:
- name: tmp
emptyDir: {}
- name: run-mysqld
emptyDir: {}
- name: mariadb-data
persistentVolumeClaim:
claimName: mariadb-nextcloud-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mariadb-nextcloud
namespace: nextcloud
spec:
selector:
app: mariadb-nextcloud
ports:
- port: 3306
targetPort: 3306
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mariadb-nextcloud-pvc
namespace: nextcloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block
resources:
requests:
storage: 10Gi
@@ -0,0 +1,35 @@
---
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: nextcloud-mariadb-password
namespace: nextcloud
spec:
length: 32
digits: 5
symbols: 3
symbolCharacters: "-_$"
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: nextcloud-mariadb-secret
namespace: nextcloud
spec:
refreshInterval: "24h"
target:
name: mariadb-nextcloud
creationPolicy: Owner
template:
data:
password: "{{ .password }}"
root-password: "{{ .password }}"
user: nextcloud
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: nextcloud-mariadb-password
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nextcloud-nextcloud
namespace: nextcloud
labels:
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nextcloud
app.kubernetes.io/instance: nextcloud
annotations:
meta.helm.sh/release-name: nextcloud
meta.helm.sh/release-namespace: nextcloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block # Rook-Ceph block storage
resources:
requests:
storage: 50Gi # whatever size you want
+111
View File
@@ -0,0 +1,111 @@
# values/nextcloud.values.yaml
#
# For chart: nextcloud/nextcloud
# Repo: https://nextcloud.github.io/helm/
#
# Optimized for large file uploads over 1Gbps link
replicaCount: 1
nextcloud:
host: nextcloud.kube.huskypup.net
username: ""
password: ""
# PHP configuration for large uploads
# Use /var/www/tmp (on Ceph PVC) instead of /tmp (on root overlay)
phpConfigs:
upload.ini: |
upload_tmp_dir = /var/www/tmp
sys_temp_dir = /var/www/tmp
# Environment variables for PHP
extraEnv:
- name: TMPDIR
value: /var/www/tmp
- name: PHP_MEMORY_LIMIT
value: "2G"
- name: PHP_UPLOAD_LIMIT
value: "10G"
configs:
oidc.config.php: |-
<?php
$CONFIG = array (
'oidc_login_provider_url' => 'https://auth.kube.huskypup.net/application/o/nextcloud/',
'oidc_login_client_id' => '',
'oidc_login_client_secret' => '',
'oidc_login_button_text' => 'Log in with Authentik',
'oidc_login_auto_redirect' => false,
'oidc_login_hide_password_form' => false,
'oidc_login_verify_ssl' => false,
'user_oidc' => array(
'httpclient.allowselfsigned' => true,
),
);
proxy.config.php: |-
<?php
$CONFIG = array (
'trusted_proxies' => array(
0 => '10.0.0.0/8',
1 => '172.16.0.0/12',
),
'overwriteprotocol' => 'https',
'overwrite.cli.url' => 'https://nextcloud.kube.huskypup.net',
'allow_local_remote_servers' => true,
);
# Use Ceph-backed temp directory for large uploads
temp.config.php: |-
<?php
$CONFIG = array (
'tempdirectory' => '/var/www/tmp',
);
existingSecret:
enabled: true
secretName: nextcloud-admin-secret
usernameKey: username
passwordKey: password
# Ingress disabled - Istio VirtualService handles routing
ingress:
enabled: false
externalDatabase:
enabled: true
type: mysql
host: mariadb-nextcloud
port: 3306
user: nextcloud
database: nextcloud
existingSecret:
enabled: true
secretName: mariadb-nextcloud
passwordKey: password
usernameKey: user
persistence:
enabled: true
storageClass: rook-ceph-block
accessMode: ReadWriteOnce
size: 50Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
resources:
requests:
cpu: 50m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
redis:
enabled: false
metrics:
enabled: false
@@ -0,0 +1,23 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: rancher-oauth
namespace: cattle-system
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: vault-backend
target:
name: rancher-oauth-secret
creationPolicy: Owner
data:
- secretKey: client-id
remoteRef:
key: secret/rancher-oauth
property: client-id
- secretKey: client-secret
remoteRef:
key: secret/rancher-oauth
property: client-secret
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: rancher
namespace: cattle-system
spec:
hosts:
- rancher.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: rancher.cattle-system.svc.cluster.local
port:
number: 80
+47
View File
@@ -0,0 +1,47 @@
# apps/rancher/values.yaml
# Rancher - Kubernetes Management Platform with Authentik SSO
hostname: rancher.kube.huskypup.net
# Exposed via Istio edge Gateway + VirtualService (apps/rancher/istio-virtualservice.yaml)
ingress:
enabled: false
# TLS terminates at Istio ingressgateway
tls: external
# Single replica for homelab (scale up if needed)
replicas: 1
# Storage class for Rancher's internal state
# Note: Rancher itself is mostly stateless; state is in etcd/k8s
resources:
requests:
cpu: 50m
memory: 512Mi
limits:
memory: 2Gi
# Allow Rancher to manage the local cluster
addLocal: "true"
# Audit logging
auditLog:
level: 1
maxAge: 7
maxBackup: 3
maxSize: 100
# Bootstrap password - will be rotated post-deploy via Authentik SSO
# This is used only for initial access before SSO is configured
bootstrapPassword: "ChangeMe-BootstrapOnly"
# Extra environment variables (not used for OIDC - configured via AuthConfig CRD post-deploy)
extraEnv: []
# Disable Rancher's bundled cert-manager (we already have it)
certmanager:
version: ""
# Feature flags
features: "multi-cluster-management=true"
@@ -0,0 +1,55 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: pg-teslamate
namespace: teslamate
spec:
imageName: ghcr.io/cloudnative-pg/postgresql:16
instances: 2
# Resource limits to prevent OOM
resources:
requests:
memory: "512Mi"
cpu: "25m"
limits:
memory: "2Gi"
cpu: "250m"
# Spread replicas across different nodes
affinity:
topologyKey: kubernetes.io/hostname
storage:
size: 10Gi
storageClass: rook-ceph-block
primaryUpdateStrategy: unsupervised
# PostgreSQL configuration for better performance
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: "2621kB"
min_wal_size: "1GB"
max_wal_size: "4GB"
bootstrap:
initdb:
database: teslamate
owner: teslamate
postInitSQL:
- ALTER USER teslamate WITH SUPERUSER
- CREATE EXTENSION IF NOT EXISTS cube
- CREATE EXTENSION IF NOT EXISTS earthdistance
monitoring:
enablePodMonitor: true
@@ -0,0 +1,39 @@
---
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
metadata:
name: teslamate-cnpg-secret
namespace: teslamate
spec:
length: 42
digits: 5
symbols: 5
symbolCharacters: "-_$@"
noUpper: false
allowRepeat: true
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: teslamate-cnpg-secret
namespace: teslamate
spec:
# how often to rotate the DB password
refreshInterval: "24h"
target:
# This will merge the generated password into the existing pg-teslamate-app secret
name: pg-teslamate-app
creationPolicy: Merge
template:
metadata:
labels:
cnpg.io/reload: "true"
data:
# Override the password field with our ESO-generated password
password: "{{ .password }}"
dataFrom:
- sourceRef:
generatorRef:
apiVersion: generators.external-secrets.io/v1alpha1
kind: Password
name: teslamate-cnpg-secret
+20
View File
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: teslamate
namespace: teslamate
spec:
parentRefs:
- name: edge
namespace: gateway
sectionName: https
hostnames:
- teslamate.kube.huskypup.net
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: teslamate
port: 4000
@@ -0,0 +1,19 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: teslamate-config
namespace: teslamate
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: teslamate-config-secret
creationPolicy: Owner
data:
- secretKey: encryption-key
remoteRef:
key: teslamate/config
property: encryption_key
@@ -0,0 +1,16 @@
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: teslamate
namespace: teslamate
spec:
hosts:
- teslamate.kube.huskypup.net
gateways:
- istio-system/edge
http:
- route:
- destination:
host: teslamate.teslamate.svc.cluster.local
port:
number: 4000
+112
View File
@@ -0,0 +1,112 @@
# Teslamate - Tesla data logger
# Uses CNPG PostgreSQL with auto-rotating passwords via ESO
image:
repository: teslamate/teslamate
tag: 1.30.1
pullPolicy: IfNotPresent
env:
TZ: America/Chicago
# PostgreSQL configuration (uses CNPG-managed database)
DATABASE_HOST: pg-teslamate-rw.teslamate.svc
DATABASE_USER: teslamate
DATABASE_NAME: teslamate
# Database password from CNPG-generated secret
DATABASE_PASS:
valueFrom:
secretKeyRef:
name: pg-teslamate-app
key: password
# Encryption key from Vault
ENCRYPTION_KEY:
valueFrom:
secretKeyRef:
name: teslamate-config-secret
key: encryption-key
# MQTT settings (disabled by default, can enable later)
DISABLE_MQTT: "true"
# Virtual host for proper URL generation
VIRTUAL_HOST: teslamate.kube.huskypup.net
# Port configuration
PORT: "4000"
service:
main:
ports:
http:
port: 4000
# Ingress disabled - Istio VirtualService handles routing
ingress:
main:
enabled: false
persistence:
import:
enabled: false
# Disable built-in postgresql (we use CNPG)
postgresql:
enabled: false
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
# Resource limits
resources:
requests:
memory: "256Mi"
cpu: "50m"
limits:
cpu: "500m"
memory: "512Mi"
# Health probes
probes:
liveness:
enabled: true
custom: true
spec:
httpGet:
path: /
port: 4000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readiness:
enabled: true
custom: true
spec:
httpGet:
path: /
port: 4000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
startup:
enabled: true
custom: true
spec:
httpGet:
path: /
port: 4000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 12
# Reloader annotations for auto-restart on secret changes
podAnnotations:
secret.reloader.stakater.com/reload: "pg-teslamate-app,teslamate-config-secret"
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "50"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://argoproj.github.io/argo-helm
chart: argo-cd
targetRevision: "*"
helm:
valueFiles:
- $values/apps/argocd/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/argocd/manifests
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+31
View File
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: esphome
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "51"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://charts.gabe565.com
chart: esphome
targetRevision: 0.15.0
helm:
valueFiles:
- $values/apps/home-assistant/esphome/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: home-assistant
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frigate
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "51"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://blakeblackshear.github.io/blakeshome-charts
chart: frigate
targetRevision: 7.8.0
helm:
valueFiles:
- $values/apps/frigate/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/frigate/manifests
destination:
server: https://kubernetes.default.svc
namespace: frigate
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitlab
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "50"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://charts.gitlab.io
chart: gitlab
targetRevision: 7.7.0
helm:
valueFiles:
- $values/apps/gitlab/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/gitlab/manifests
destination:
server: https://kubernetes.default.svc
namespace: gitlab
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+25
View File
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: guacamole
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "51"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/guacamole/manifests
destination:
server: https://kubernetes.default.svc
namespace: guacamole
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: home-assistant
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "51"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://geek-cookbook.github.io/charts/
chart: home-assistant
targetRevision: 13.5.0
helm:
valueFiles:
- $values/apps/home-assistant/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/home-assistant/manifests
destination:
server: https://kubernetes.default.svc
namespace: home-assistant
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+25
View File
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-app-routes
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "52"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/istio/manifests/apps
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: n8n
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "50"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://riatlas.github.io/chart__n8n
chart: n8n
targetRevision: "*"
helm:
valueFiles:
- $values/apps/n8n/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/n8n/manifests
destination:
server: https://kubernetes.default.svc
namespace: n8n
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+25
View File
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nessus
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "51"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/nessus/manifests
destination:
server: https://kubernetes.default.svc
namespace: nessus
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nextcloud
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "50"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://nextcloud.github.io/helm/
chart: nextcloud
targetRevision: "*"
helm:
valueFiles:
- $values/apps/nextcloud/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/nextcloud/manifests
destination:
server: https://kubernetes.default.svc
namespace: nextcloud
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: rancher
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "51"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://releases.rancher.com/server-charts/stable
chart: rancher
targetRevision: 2.13.2
helm:
valueFiles:
- $values/apps/rancher/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/rancher/manifests
destination:
server: https://kubernetes.default.svc
namespace: cattle-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: teslamate
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "50"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: applications
sources:
- repoURL: https://geek-cookbook.github.io/charts/
chart: teslamate
targetRevision: 7.2.0
helm:
valueFiles:
- $values/apps/teslamate/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: apps/teslamate/manifests
destination:
server: https://kubernetes.default.svc
namespace: teslamate
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+22
View File
@@ -0,0 +1,22 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: argocd-hook-sa
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-5"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argocd-hook-sa-admin
annotations:
argocd.argoproj.io/sync-wave: "-5"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: argocd-hook-sa
namespace: argocd
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: authentik
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "23"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://charts.goauthentik.io
chart: authentik
targetRevision: 2026.2.1
helm:
valueFiles:
- $values/infrastructure/authentik/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/authentik/manifests
destination:
server: https://kubernetes.default.svc
namespace: authentik
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cert-manager
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://charts.jetstack.io
chart: cert-manager
targetRevision: v1.13.2
helm:
valueFiles:
- $values/infrastructure/cert-manager/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/cert-manager/manifests
destination:
server: https://kubernetes.default.svc
namespace: cert-manager
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: checkov-scanner
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "43"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/checkov/manifests
destination:
server: https://kubernetes.default.svc
namespace: checkov
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cilium
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://helm.cilium.io
chart: cilium
targetRevision: 1.17.1
helm:
valueFiles:
- $values/infrastructure/cilium/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/cilium/manifests
destination:
server: https://kubernetes.default.svc
namespace: kube-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+25
View File
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cnpg
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "12"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: https://cloudnative-pg.github.io/charts
chart: cloudnative-pg
targetRevision: "*"
destination:
server: https://kubernetes.default.svc
namespace: cnpg-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: crowdsec
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "22"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://crowdsecurity.github.io/helm-charts
chart: crowdsec
targetRevision: "*"
helm:
valueFiles:
- $values/infrastructure/crowdsec/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/crowdsec/manifests
destination:
server: https://kubernetes.default.svc
namespace: crowdsec
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: envoy-edge
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "7"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/envoy-gateway/manifests
destination:
server: https://kubernetes.default.svc
namespace: gateway
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: envoy-gateway
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "6"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://gateway.envoyproxy.io/charts
chart: gateway-helm
targetRevision: v1.6.3
helm:
valueFiles:
- $values/infrastructure/envoy-gateway/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: envoy-gateway-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-dns
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "30"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://kubernetes-sigs.github.io/external-dns/
chart: external-dns
targetRevision: 1.20.0
helm:
valueFiles:
- $values/infrastructure/external-dns/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/external-dns/manifests
destination:
server: https://kubernetes.default.svc
namespace: external-dns
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-secrets
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "21"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://charts.external-secrets.io
chart: external-secrets
targetRevision: 0.20.4
helm:
valueFiles:
- $values/infrastructure/external-secrets/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: external-secrets
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+37
View File
@@ -0,0 +1,37 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: grafana
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "41"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://grafana.github.io/helm-charts
chart: grafana
targetRevision: "*"
helm:
valueFiles:
- $values/infrastructure/grafana/values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/grafana/manifests
- repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/grafana/dashboards
destination:
server: https://kubernetes.default.svc
namespace: grafana
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-base
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: https://istio-release.storage.googleapis.com/charts
chart: base
targetRevision: 1.28.3
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+31
View File
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-cni
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "3"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://istio-release.storage.googleapis.com/charts
chart: cni
targetRevision: 1.29.0
helm:
valueFiles:
- $values/infrastructure/istio/cni-values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: kube-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-edge
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "6"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/istio/manifests/edge
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-ingressgateway
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "5"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://istio-release.storage.googleapis.com/charts
chart: gateway
targetRevision: 1.29.0
helm:
valueFiles:
- $values/infrastructure/istio/ingressgateway-values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,25 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-mesh-config
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "42"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: <GIT_REPO_URL>
targetRevision: main
path: infrastructure/istio/manifests/mesh
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
+31
View File
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istiod
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "4"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://istio-release.storage.googleapis.com/charts
chart: istiod
targetRevision: 1.29.0
helm:
valueFiles:
- $values/infrastructure/istio/istiod-values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
@@ -0,0 +1,31 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kiali-operator
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "42"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
sources:
- repoURL: https://kiali.org/helm-charts
chart: kiali-operator
targetRevision: "*"
helm:
valueFiles:
- $values/infrastructure/kiali/operator-values.yaml
- repoURL: <GIT_REPO_URL>
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: kiali-operator
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true

Some files were not shown because too many files have changed in this diff Show More