diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4a2dde1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,453 @@ +# Application Helm Charts Guide + +This document provides guidelines for creating and maintaining Helm charts in this homelab project. + +## Project Structure + +``` +apps/ +├── charts/ # Individual application Helm charts +│ ├── app-name/ +│ │ ├── Chart.yaml +│ │ ├── values.yaml +│ │ └── templates/ +│ │ ├── deployment.yaml +│ │ ├── service.yaml +│ │ ├── pvc.yaml +│ │ ├── client.yaml # OIDC client configuration +│ │ ├── database.yaml # Database provisioning +│ │ ├── secret.yaml # Secret generation +│ │ └── external-http-service.yaml +│ └── ... +└── root/ # ArgoCD ApplicationSet for auto-discovery + ├── Chart.yaml + ├── values.yaml + └── templates/ + ├── applicationset.yaml + └── project.yaml + +foundation/ +├── charts/ # Foundation service Helm charts +│ └── ... +└── root/ # ArgoCD ApplicationSet for foundation services + +shared/ +├── charts/ # Shared service Helm charts +│ └── ... +└── root/ # ArgoCD ApplicationSet for shared services +``` + +## ArgoCD ApplicationSets + +This project uses three separate ArgoCD ApplicationSets to manage different categories of services: + +1. **apps/** - Individual applications (web apps, tools, services) +2. **foundation/** - Core infrastructure for the cluster (monitoring, certificates, operators) +3. **shared/** - Infrastructure shared between applications (databases, message queues, caches) + +Each category has its own `root/` chart containing an ApplicationSet that auto-discovers and deploys charts from its respective `charts/` directory. + +## Creating a New Application Chart + +### 1. Basic Chart Structure + +Create a new directory under `apps/charts/` with the following structure: + +```bash +mkdir -p apps/charts/my-app/templates +``` + +#### Chart.yaml +```yaml +apiVersion: v2 +version: 1.0.0 +name: my-app +``` + +#### values.yaml +```yaml +image: + repository: docker.io/org/my-app + tag: v1.0.0 + pullPolicy: IfNotPresent +subdomain: my-app +``` + +### 2. Core Templates + +#### Deployment Template +Create `templates/deployment.yaml`: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ .Release.Name }}" +spec: + strategy: + type: Recreate + replicas: 1 + revisionHistoryLimit: 0 + selector: + matchLabels: + app: "{{ .Release.Name }}" + template: + metadata: + labels: + app: "{{ .Release.Name }}" + spec: + containers: + - name: "{{ .Release.Name }}" + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + ports: + - name: http + containerPort: 8080 + protocol: TCP + livenessProbe: + tcpSocket: + port: http + readinessProbe: + tcpSocket: + port: http + volumeMounts: + - mountPath: /data + name: data + volumes: + - name: data + persistentVolumeClaim: + claimName: "{{ .Release.Name }}-data" +``` + +#### Service Template +Create `templates/service.yaml`: +```yaml +apiVersion: v1 +kind: Service +metadata: + name: "{{ .Release.Name }}" +spec: + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: "{{ .Release.Name }}" +``` + +#### Persistent Volume Claim +Create `templates/pvc.yaml`: +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: "{{ .Release.Name }}-data" +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi +``` + +## Custom Resource Definitions (CRDs) + +This project uses several custom resources that are managed by operators in the cluster: + +### 1. OIDC Client (OpenID Connect Authentication) + +The `OidcClient` resource automatically provisions OAuth2/OIDC clients with your identity provider. + +Create `templates/client.yaml`: +```yaml +apiVersion: homelab.mortenolsen.pro/v1 +kind: OidcClient +metadata: + name: '{{ .Release.Name }}' +spec: + environment: '{{ .Values.globals.environment }}' + redirectUris: + - path: /oauth/oidc/callback + subdomain: '{{ .Values.subdomain }}' + matchingMode: strict +``` + +**What it does:** +- Creates an OIDC client in your identity provider (e.g., Authentik) +- Generates a Kubernetes secret named `{{ .Release.Name }}-client` containing: + - `clientId`: The OAuth client ID + - `clientSecret`: The OAuth client secret + - `configuration`: The OIDC provider URL + +**Using in deployment:** +```yaml +env: + - name: OAUTH_CLIENT_ID + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-client" + key: clientId + - name: OAUTH_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-client" + key: clientSecret + - name: OPENID_PROVIDER_URL + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-client" + key: configuration +``` + +### 2. PostgreSQL Database + +The `PostgresDatabase` resource automatically provisions PostgreSQL databases. + +Create `templates/database.yaml`: +```yaml +apiVersion: homelab.mortenolsen.pro/v1 +kind: PostgresDatabase +metadata: + name: '{{ .Release.Name }}' +spec: + environment: '{{ .Values.globals.environment }}' +``` + +**What it does:** +- Creates a PostgreSQL database with the same name as your release +- Creates a user with appropriate permissions +- Generates a Kubernetes secret named `{{ .Release.Name }}-database` containing: + - `url`: Complete PostgreSQL connection URL + - `host`: Database hostname + - `port`: Database port + - `database`: Database name + - `username`: Database username + - `password`: Database password + +**Using in deployment:** +```yaml +env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-database" + key: url +``` + +### 3. Secret Generation + +The `GenerateSecret` resource creates secure random secrets. + +Create `templates/secret.yaml`: +```yaml +apiVersion: homelab.mortenolsen.pro/v1 +kind: GenerateSecret +metadata: + name: "{{ .Release.Name }}-secrets" +spec: + fields: + - name: encryptionkey + encoding: hex # Options: hex, base64, alphanumeric + length: 64 # Length in bytes (before encoding) + - name: apitoken + encoding: base64 + length: 32 +``` + +**What it does:** +- Generates cryptographically secure random values +- Creates a Kubernetes secret with the specified fields +- Supports different encoding formats for different use cases + +**Using in deployment:** +```yaml +env: + - name: ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: "{{ .Release.Name }}-secrets" + key: encryptionkey +``` + +### 4. External HTTP Service + +The `ExternalHttpService` resource configures ingress routing for your application. + +Create `templates/external-http-service.yaml`: +```yaml +apiVersion: homelab.mortenolsen.pro/v1 +kind: ExternalHttpService +metadata: + name: '{{ .Release.Name }}' +spec: + environment: '{{ .Values.globals.environment }}' + subdomain: '{{ .Values.subdomain }}' + destination: + host: '{{ .Release.Name }}.{{ .Release.Namespace }}.svc.cluster.local' + port: + number: 80 +``` + +**What it does:** +- Creates ingress routes for your application +- Configures subdomain routing (e.g., `myapp.yourdomain.com`) +- Handles TLS termination automatically +- Integrates with your service mesh (if applicable) + +## Best Practices + +### 1. Naming Conventions +- Use `{{ .Release.Name }}` consistently for all resource names +- Suffix resource names appropriately: `-data`, `-secrets`, `-client`, `-database` + +### 2. Container Configuration +- Always specify health checks (liveness and readiness probes) +- Use named ports (e.g., `http`, `grpc`) instead of port numbers +- Set `revisionHistoryLimit: 0` to prevent accumulation of old ReplicaSets + +### 3. Environment Variables +- Never hardcode secrets in values.yaml +- Use secretKeyRef to reference generated secrets +- Group related environment variables together + +### 4. Persistent Storage +- Always use PVCs for stateful data +- Consider storage requirements carefully (start with reasonable defaults) +- Mount data at standard paths for the application + +### 5. OIDC Integration +- Set `ENABLE_SIGNUP: "false"` if using OIDC +- Enable OIDC signup with `ENABLE_OAUTH_SIGNUP: "true"` +- Configure email merging if needed with `OAUTH_MERGE_ACCOUNTS_BY_EMAIL` + +### 6. Database Usage +- Only include database.yaml if the app needs PostgreSQL +- Applications should support DATABASE_URL environment variable +- Consider connection pooling settings for production + +## Disabling Applications + +To temporarily disable an application, rename its directory with `.disabled` suffix: +```bash +mv apps/charts/my-app apps/charts/my-app.disabled +``` + +The ArgoCD ApplicationSet will automatically exclude directories matching `*.disabled`. + +## Testing Your Chart + +1. **Lint your chart:** + ```bash + helm lint apps/charts/my-app + ``` + +2. **Render templates locally:** + ```bash + helm template my-app apps/charts/my-app + ``` + +3. **Dry run installation:** + ```bash + helm install my-app apps/charts/my-app --dry-run --debug + ``` + +## Deployment Workflow + +**IMPORTANT:** There is no test environment. When creating or modifying applications: + +1. **Make changes directly to the files** - The agent will write changes to the actual chart files +2. **User deploys the changes** - After changes are made, the user must deploy them to the cluster +3. **Debug with kubectl** - If issues arise after deployment, agents can use kubectl to: + - Check pod status and logs + - Inspect generated resources + - Verify secret creation + - Troubleshoot configuration issues + +**Note:** Agents cannot deploy applications themselves. They can only: +- Create and modify chart files +- Use kubectl to investigate deployment issues +- Provide debugging assistance and recommendations + +## Common Patterns + +### Application with OIDC + Database +For apps requiring both authentication and database: +- Include `client.yaml` for OIDC +- Include `database.yaml` for PostgreSQL +- Reference both secrets in deployment + +### Stateless Applications +For simple stateless apps: +- Omit `pvc.yaml` +- Remove volume mounts from deployment +- Consider using `Deployment` scaling if appropriate + +### Background Services +For services without web interface: +- Omit `external-http-service.yaml` +- Omit `client.yaml` (no OIDC needed) +- Focus on service discovery within cluster + +## Troubleshooting + +### Secret Not Found +If secrets are not being created: +1. Check that the CRD controller is running +2. Verify the `environment` value matches your setup +3. Check controller logs for provisioning errors + +### OIDC Issues +1. Verify redirect URIs match exactly +2. Check that the identity provider is accessible +3. Ensure the client secret is being properly mounted + +### Database Connection +1. Verify the database operator is running +2. Check network policies between namespaces +3. Ensure the database server has capacity + +## Global Values + +Applications can access global values through `{{ .Values.globals }}`: +- `environment`: The deployment environment (e.g., "production", "staging") +- Additional values can be added at the root chart level + +## Maintenance + +### Updating Images +1. Update the tag in `values.yaml`: + ```yaml + tag: v1.0.0 # Use semantic version tags only + ``` +2. **Note:** Do not include SHA digests in tags. Immutable digests are automatically added later by Renovate + +### Renovate Integration +The project uses Renovate for automated dependency updates. Configure in `renovate.json5` to: +- Auto-update container images +- Create pull requests for updates +- Group related updates + +### Backup Considerations +For applications with persistent data: +1. Consider implementing backup CronJobs +2. Use volume snapshots if available +3. Export data regularly for critical applications + +## Contributing + +When adding new applications: +1. Follow the existing patterns and conventions +2. Document any special requirements in the chart's README +3. Consider security implications of all configurations +4. Update this document if introducing new patterns + +## Maintaining This Document + +**IMPORTANT:** When making changes to the project structure, patterns, or custom resources: +- Keep this AGENTS.md file up to date with any changes +- Document new CRDs or custom resources as they are added +- Update examples if the patterns change +- Add new sections for significant new features or patterns +- Ensure all code examples remain accurate and tested + +This document serves as the primary reference for creating and maintaining applications in this project. Keeping it current ensures consistency and helps onboard new contributors. \ No newline at end of file diff --git a/charts/monitoring/Chart.yaml b/charts/monitoring/Chart.yaml deleted file mode 100644 index 9683fee..0000000 --- a/charts/monitoring/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -version: 1.0.0 -name: monitoring diff --git a/charts/monitoring/templates/_falco.yaml b/charts/monitoring/templates/_falco.yaml deleted file mode 100644 index 0175dd8..0000000 --- a/charts/monitoring/templates/_falco.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: '{{ .Release.Name }}-falco' -spec: - interval: 1h - url: https://falcosecurity.github.io/charts - ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: '{{ .Release.Name }}-falco' -spec: - chart: - spec: - chart: falco - reconcileStrategy: ChartVersion - sourceRef: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - name: '{{ .Release.Name }}-falco' - namespace: '{{ .Release.Namespace }}' - interval: 1h - values: {} diff --git a/charts/monitoring/templates/_kube-prometheus-stack.yaml b/charts/monitoring/templates/_kube-prometheus-stack.yaml deleted file mode 100644 index bc8df51..0000000 --- a/charts/monitoring/templates/_kube-prometheus-stack.yaml +++ /dev/null @@ -1,53 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: "{{ .Release.Name }}-prometheus-community" -spec: - interval: 1h - url: https://prometheus-community.github.io/helm-charts/ - ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: "{{ .Release.Name }}-prometheus-community" -spec: - chart: - spec: - chart: kube-prometheus-stack - reconcileStrategy: ChartVersion - sourceRef: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - name: "{{ .Release.Name }}-prometheus-community" - namespace: "{{ .Release.Namespace }}" - interval: 1h - values: - grafana: - env: - GF_SERVER_ROOT_URL: https://grafana.olsen.cloud # TODO - ---- -apiVersion: homelab.mortenolsen.pro/v1 -kind: HttpService -metadata: - name: "{{ .Release.Name }}-prometheus-community" -spec: - environment: "{{ .Values.globals.environment }}" - subdomain: "{{ .Values.grafana.subdomain }}" - destination: - host: "{{ .Release.Name }}-prometheus-community-grafana.{{ .Release.Namespace }}.svc.cluster.local" - port: - number: 80 - ---- -apiVersion: homelab.mortenolsen.pro/v1 -kind: OidcClient -metadata: - name: "{{ .Release.Name }}-grafana" -spec: - environment: "{{ .Values.globals.environment }}" - redirectUris: - - path: /login/generic_oauth - subdomain: "{{ .Values.grafana.subdomain }}" - matchingMode: strict diff --git a/charts/monitoring/templates/_loki.yaml b/charts/monitoring/templates/_loki.yaml deleted file mode 100644 index 2a1bafe..0000000 --- a/charts/monitoring/templates/_loki.yaml +++ /dev/null @@ -1,121 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: '{{ .Release.Name }}-loki' -spec: - interval: 1h - url: https://grafana.github.io/helm-charts - ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: '{{ .Release.Name }}-loki' -spec: - chart: - spec: - chart: loki - reconcileStrategy: ChartVersion - sourceRef: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - name: '{{ .Release.Name }}-loki' - namespace: '{{ .Release.Namespace }}' - interval: 1h - values: - deploymentMode: SingleBinary - loki: - auth_enabled: false - server: - http_listen_port: 3100 - - # memberlist: - # join_members: - # - loki-memberlist - - schemaConfig: - configs: - - from: 2020-05-15 - store: tsdb - object_store: filesystem - schema: v13 - index: - prefix: index_ - period: 24h - - storage: - type: filesystem - - storage_config: - filesystem: - directory: /loki/chunks - - limits_config: - reject_old_samples: true - reject_old_samples_max_age: 168h - max_cache_freshness_per_query: 10m - split_queries_by_interval: 15m - volume_enabled: true - - common: - path_prefix: /loki - storage: - filesystem: - chunks_directory: /loki/chunks - rules_directory: /loki/rules - replication_factor: 1 - ring: - instance_addr: 127.0.0.1 - kvstore: - store: inmemory - - # Enable persistent storage - singleBinary: - persistence: - enabled: true - size: 10Gi - storageClass: '{{ .Values.globals.environment }}' # Uses default storage class - extraVolumeMounts: - - name: storage - mountPath: /loki - - backend: - replicas: 0 - read: - replicas: 0 - write: - replicas: 0 - - ingester: - replicas: 0 - querier: - replicas: 0 - queryFrontend: - replicas: 0 - queryScheduler: - replicas: 0 - distributor: - replicas: 0 - compactor: - replicas: 0 - indexGateway: - replicas: 0 - bloomCompactor: - replicas: 0 - bloomGateway: - replicas: 0 - promtail: - enabled: true - config: - snippets: - extraScrapeConfigs: | - - job_name: kubernetes-pods - kubernetes_sd_configs: - - role: pod - relabel_configs: - - source_labels: ["__meta_kubernetes_pod_container_name"] - target_label: "container" - - source_labels: ["__meta_kubernetes_pod_name"] - target_label: "pod" - - source_labels: ["__meta_kubernetes_pod_namespace"] - target_label: "namespace" diff --git a/charts/monitoring/templates/kyverno.yaml b/charts/monitoring/templates/kyverno.yaml deleted file mode 100644 index 8845eb5..0000000 --- a/charts/monitoring/templates/kyverno.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: '{{ .Release.Name }}-kyverno' -spec: - interval: 1h - url: https://kyverno.github.io/kyverno/ - ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: '{{ .Release.Name }}-kyverno' -spec: - chart: - spec: - chart: kyverno - reconcileStrategy: ChartVersion - sourceRef: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - name: '{{ .Release.Name }}-kyverno' - namespace: '{{ .Release.Namespace }}' - interval: 1h - values: {} diff --git a/charts/monitoring/templates/trivy.yaml b/charts/monitoring/templates/trivy.yaml deleted file mode 100644 index e7cfa89..0000000 --- a/charts/monitoring/templates/trivy.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: '{{ .Release.Name }}-aqua' -spec: - interval: 1h - url: https://aquasecurity.github.io/helm-charts/ - ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: '{{ .Release.Name }}-aqua' -spec: - chart: - spec: - chart: trivy-operator - reconcileStrategy: ChartVersion - sourceRef: - apiVersion: source.toolkit.fluxcd.io/v1 - kind: HelmRepository - name: '{{ .Release.Name }}-aqua' - namespace: '{{ .Release.Namespace }}' - interval: 1h - values: {} diff --git a/charts/monitoring/values.yaml b/charts/monitoring/values.yaml deleted file mode 100644 index 5718646..0000000 --- a/charts/monitoring/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -globals: - environment: prod -grafana: - subdomain: grafana diff --git a/charts/rules/Chart.yaml b/charts/rules/Chart.yaml deleted file mode 100644 index 9163119..0000000 --- a/charts/rules/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -version: '1.0.0' -name: rules diff --git a/charts/rules/templates/enforce-read-only-root-filesystem.yaml b/charts/rules/templates/enforce-read-only-root-filesystem.yaml deleted file mode 100644 index c60018d..0000000 --- a/charts/rules/templates/enforce-read-only-root-filesystem.yaml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: kyverno.io/v1 -kind: Policy -metadata: - name: enforce-immutable-filesystem - annotations: - policies.kyverno.io/category: Security - policies.kyverno.io/severity: medium - policies.kyverno.io/description: | - This policy automatically sets 'readOnlyRootFilesystem: true' for all containers - within new Pods, enforcing an immutable root filesystem. This enhances security - by preventing applications from writing to their root filesystem at runtime, - making it harder for attackers to persist changes or introduce malware. -spec: - validationFailureAction: Audit - rules: - - name: enforce-read-only-root-filesystem - match: - any: - - resources: - kinds: - - Pod - mutate: - patchStrategicMerge: - spec: - containers: - - (name): '*' # Apply to all containers - securityContext: - readOnlyRootFilesystem: true - initContainers: - - (name): '*' # Apply to all init containers - securityContext: - readOnlyRootFilesystem: true