diff --git a/.claude/commands/helm-adapt.md b/.claude/commands/helm-adapt.md index 1f1c2a0..4e07edc 100644 --- a/.claude/commands/helm-adapt.md +++ b/.claude/commands/helm-adapt.md @@ -120,4 +120,4 @@ spec: - Fix any amd64 image to arm64 equivalent 4. Create `pv-.yaml` in the service folder with correct path and sizes. 5. Create `pvc-.yaml` only if the workload is a Deployment (not StatefulSet). -6. Create `NOTE.md` with helm install/upgrade/delete commands, PV apply commands, and useful kubectl check/log commands — following the style of `../immich/notes.md`. +6. Create `README.md` with helm install/upgrade/delete commands, PV apply commands, and useful kubectl check/log commands — following the style of `../immich/README.md`. diff --git a/ai-teacher/Chart.yaml b/ai-teacher/Chart.yaml new file mode 100644 index 0000000..6157e7d --- /dev/null +++ b/ai-teacher/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: ai-teacher +description: AI Teacher application — RAG-powered learning assistant +type: application +version: 0.1.0 +appVersion: "latest" diff --git a/ai-teacher/README.md b/ai-teacher/README.md new file mode 100644 index 0000000..660ffec --- /dev/null +++ b/ai-teacher/README.md @@ -0,0 +1,67 @@ +# ai-teacher + +RAG-powered learning assistant. +Namespace: `ai-teacher` + +## Registry pull secret (run once) + +```bash +kubectl create secret docker-registry zot-pull-secret \ + --docker-server=zot.immich-ad.ovh \ + --docker-username= \ + --docker-password= \ + -n ai-teacher +``` + +## Helm + +```bash +# Install +helm install ai-teacher . -n ai-teacher --create-namespace -f values.yaml + +# Upgrade +helm upgrade ai-teacher . -n ai-teacher -f values.yaml + +# Delete +helm delete ai-teacher -n ai-teacher +``` + +## Pods & Services + +```bash +kubectl -n ai-teacher get pods +kubectl -n ai-teacher get svc +kubectl -n ai-teacher describe pod +``` + +## Logs + +```bash +# Backend +kubectl -n ai-teacher logs deploy/ai-teacher-backend --follow + +# Frontend +kubectl -n ai-teacher logs deploy/ai-teacher-frontend --follow +``` + +## Ingress + +```bash +kubectl -n ai-teacher get ingress +kubectl -n ai-teacher describe ingress ai-teacher +``` + +## Certificate (cert-manager) + +```bash +kubectl -n ai-teacher get certificate +kubectl -n ai-teacher describe certificate ai-teacher-tls +kubectl -n ai-teacher get challenges +``` + +## Secret + +```bash +# Check secret keys are present +kubectl -n ai-teacher get secret ai-teacher -o jsonpath='{.data}' | jq 'keys' +``` diff --git a/ai-teacher/templates/_helpers.tpl b/ai-teacher/templates/_helpers.tpl new file mode 100644 index 0000000..a34b241 --- /dev/null +++ b/ai-teacher/templates/_helpers.tpl @@ -0,0 +1,15 @@ +{{- define "ai-teacher.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-ai-teacher" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{- define "ai-teacher.secretName" -}} +{{- if .Values.backend.existingSecret }} +{{- .Values.backend.existingSecret }} +{{- else }} +{{- printf "%s-secret" (include "ai-teacher.fullname" .) }} +{{- end }} +{{- end }} diff --git a/ai-teacher/templates/backend-deployment.yaml b/ai-teacher/templates/backend-deployment.yaml new file mode 100644 index 0000000..715fad6 --- /dev/null +++ b/ai-teacher/templates/backend-deployment.yaml @@ -0,0 +1,90 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ai-teacher.fullname" . }}-backend + labels: + app: {{ include "ai-teacher.fullname" . }}-backend +spec: + replicas: {{ .Values.backend.replicaCount }} + selector: + matchLabels: + app: {{ include "ai-teacher.fullname" . }}-backend + template: + metadata: + labels: + app: {{ include "ai-teacher.fullname" . }}-backend + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: backend + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: 8080 + env: + - name: DB_URL + value: {{ .Values.backend.env.DB_URL | quote }} + - name: DB_USERNAME + value: {{ .Values.backend.env.DB_USERNAME | quote }} + - name: UPLOAD_ENABLED + value: {{ .Values.backend.env.UPLOAD_ENABLED | quote }} + - name: DELETE_ENABLED + value: {{ .Values.backend.env.DELETE_ENABLED | quote }} + - name: MARKER_BASE_URL + value: {{ .Values.backend.env.MARKER_BASE_URL | quote }} + - name: VISION_MIN_INTERVAL_MS + value: {{ .Values.backend.env.VISION_MIN_INTERVAL_MS | quote }} + - name: S3_ENDPOINT + value: {{ .Values.backend.env.S3_ENDPOINT | quote }} + - name: S3_BUCKET + value: {{ .Values.backend.env.S3_BUCKET | quote }} + - name: S3_REGION + value: {{ .Values.backend.env.S3_REGION | quote }} + # Sensitive — from Secret + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: DB_PASSWORD + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: OPENAI_API_KEY + - name: APP_AUTH_USERNAME + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: APP_AUTH_USERNAME + - name: APP_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: APP_PASSWORD + - name: S3_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: S3_ACCESS_KEY_ID + - name: S3_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: S3_SECRET_ACCESS_KEY + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} + # readinessProbe: + # httpGet: + # path: /actuator/health + # port: 8080 + # initialDelaySeconds: 20 + # periodSeconds: 10 + # livenessProbe: + # httpGet: + # path: /actuator/health + # port: 8080 + # initialDelaySeconds: 40 + # periodSeconds: 30 diff --git a/ai-teacher/templates/backend-service.yaml b/ai-teacher/templates/backend-service.yaml new file mode 100644 index 0000000..b79e072 --- /dev/null +++ b/ai-teacher/templates/backend-service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ai-teacher.fullname" . }}-backend +spec: + selector: + app: {{ include "ai-teacher.fullname" . }}-backend + ports: + - port: 8080 + targetPort: 8080 diff --git a/ai-teacher/templates/frontend-configmap.yaml b/ai-teacher/templates/frontend-configmap.yaml new file mode 100644 index 0000000..883b1f2 --- /dev/null +++ b/ai-teacher/templates/frontend-configmap.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ai-teacher.fullname" . }}-nginx +data: + default.conf: | + server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Proxy API calls to the backend service + location ^~ /api/{ + proxy_pass http://{{ include "ai-teacher.fullname" . }}-backend:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 120s; + } + + # Vue Router — serve index.html for all non-asset routes + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets aggressively (content-hashed filenames) + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + gzip on; + gzip_types text/plain text/css application/javascript application/json; + gzip_min_length 1024; + } diff --git a/ai-teacher/templates/frontend-deployment.yaml b/ai-teacher/templates/frontend-deployment.yaml new file mode 100644 index 0000000..4fe1599 --- /dev/null +++ b/ai-teacher/templates/frontend-deployment.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ai-teacher.fullname" . }}-frontend + labels: + app: {{ include "ai-teacher.fullname" . }}-frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + app: {{ include "ai-teacher.fullname" . }}-frontend + template: + metadata: + labels: + app: {{ include "ai-teacher.fullname" . }}-frontend + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: frontend + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: 80 + env: + - name: VITE_UPLOAD_ENABLED + value: {{ .Values.frontend.env.VITE_UPLOAD_ENABLED | quote }} + - name: VITE_DELETE_ENABLED + value: {{ .Values.frontend.env.VITE_DELETE_ENABLED | quote }} + - name: VITE_APP_AUTH_USERNAME + valueFrom: + secretKeyRef: + name: {{ include "ai-teacher.secretName" . }} + key: APP_AUTH_USERNAME + volumeMounts: + - name: nginx-conf + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + resources: + {{- toYaml .Values.frontend.resources | nindent 12 }} + volumes: + - name: nginx-conf + configMap: + name: {{ include "ai-teacher.fullname" . }}-nginx diff --git a/ai-teacher/templates/frontend-service.yaml b/ai-teacher/templates/frontend-service.yaml new file mode 100644 index 0000000..08249a5 --- /dev/null +++ b/ai-teacher/templates/frontend-service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ai-teacher.fullname" . }}-frontend +spec: + selector: + app: {{ include "ai-teacher.fullname" . }}-frontend + ports: + - port: 80 + targetPort: 80 diff --git a/ai-teacher/templates/ingress.yaml b/ai-teacher/templates/ingress.yaml new file mode 100644 index 0000000..fb39392 --- /dev/null +++ b/ai-teacher/templates/ingress.yaml @@ -0,0 +1,28 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "ai-teacher.fullname" . }} + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + traefik.ingress.kubernetes.io/router.entrypoints: websecure +spec: + ingressClassName: {{ .Values.ingress.className | quote }} + {{- if .Values.ingress.tls }} + tls: + - hosts: + - {{ .Values.ingress.host | quote }} + secretName: {{ .Values.ingress.tlsSecretName | quote }} + {{- end }} + rules: + - host: {{ .Values.ingress.host | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "ai-teacher.fullname" . }}-frontend + port: + number: 80 +{{- end }} diff --git a/ai-teacher/templates/secret.yaml b/ai-teacher/templates/secret.yaml new file mode 100644 index 0000000..112586c --- /dev/null +++ b/ai-teacher/templates/secret.yaml @@ -0,0 +1,17 @@ +{{- if not .Values.backend.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "ai-teacher.fullname" . }}-secret + labels: + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +type: Opaque +stringData: + DB_PASSWORD: {{ .Values.backend.secret.DB_PASSWORD | quote }} + OPENAI_API_KEY: {{ .Values.backend.secret.OPENAI_API_KEY | quote }} + APP_AUTH_USERNAME: {{ .Values.backend.secret.APP_AUTH_USERNAME | quote }} + APP_PASSWORD: {{ .Values.backend.secret.APP_PASSWORD | quote }} + S3_ACCESS_KEY_ID: {{ .Values.backend.secret.S3_ACCESS_KEY_ID | quote }} + S3_SECRET_ACCESS_KEY: {{ .Values.backend.secret.S3_SECRET_ACCESS_KEY | quote }} +{{- end }} diff --git a/ai-teacher/values.yaml b/ai-teacher/values.yaml new file mode 100644 index 0000000..27ee5dd --- /dev/null +++ b/ai-teacher/values.yaml @@ -0,0 +1,68 @@ +registry: zot.immich-ad.ovh + +imagePullSecrets: + - name: zot-pull-secret + +backend: + image: + repository: zot.immich-ad.ovh/ai-teacher-backend + tag: latest + pullPolicy: Always + replicaCount: 1 + resources: + requests: + memory: "64Mi" + cpu: "250m" + limits: + memory: "256Mi" + cpu: "1000m" + env: + # Database + DB_URL: "jdbc:postgresql://shared-postgres-rw.db.svc.cluster.local:5432/aiteacher" + DB_USERNAME: "user" + # Feature flags + UPLOAD_ENABLED: "false" + DELETE_ENABLED: "false" + # Marker service + MARKER_BASE_URL: "http://marker:8000" + VISION_MIN_INTERVAL_MS: "2000" + # S3 + S3_ENDPOINT: "https://s3.immich-ad.ovh" + S3_BUCKET: "aiteacher" + S3_REGION: "garage" + + # Sensitive values — override these or use existingSecret + secret: + DB_PASSWORD: "password" + OPENAI_API_KEY: "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" + APP_AUTH_USERNAME: "jane" + APP_PASSWORD: "210687" + S3_ACCESS_KEY_ID: "GK9ee0a503e1ec4bc53ca25cb4" + S3_SECRET_ACCESS_KEY: "8ee84f6b0f2ffd7b4c5d73dc68536802f87c96c47fde133471aaac01c9571774" + + # Set to an existing Secret name to skip creating one (must contain all keys above) + existingSecret: "" + +frontend: + image: + repository: zot.immich-ad.ovh/ai-teacher-frontend + tag: latest + pullPolicy: Always + replicaCount: 1 + resources: + requests: + memory: "12Mi" + cpu: "50m" + limits: + memory: "64Mi" + cpu: "200m" + env: + VITE_UPLOAD_ENABLED: false + VITE_DELETE_ENABLED: false + +ingress: + enabled: true + className: traefik + host: ai-teacher.immich-ad.ovh + tls: true + tlsSecretName: ai-teacher-tls \ No newline at end of file diff --git a/bitwarden/notes.md b/bitwarden/README.md similarity index 100% rename from bitwarden/notes.md rename to bitwarden/README.md diff --git a/gitea/NOTES.MD b/gitea/README.md similarity index 100% rename from gitea/NOTES.MD rename to gitea/README.md diff --git a/immich/notes.md b/immich/README.md similarity index 100% rename from immich/notes.md rename to immich/README.md diff --git a/nextcloud/NOTES.md b/nextcloud/README.md similarity index 100% rename from nextcloud/NOTES.md rename to nextcloud/README.md diff --git a/observability/notes.md b/observability/README.md similarity index 100% rename from observability/notes.md rename to observability/README.md diff --git a/readme.md b/readme.md deleted file mode 100644 index b9cf175..0000000 --- a/readme.md +++ /dev/null @@ -1,124 +0,0 @@ -# Kubernetes Cluster Configuration - -A comprehensive Helm-based Kubernetes cluster setup with multiple applications and services organized by function. - -## 📁 Project Structure - -### Core Infrastructure - -#### **Cluster** -- Storage class configuration for persistent volumes - -#### **Traefik** (`traefik/`) -- Ingress controller and reverse proxy -- Routes external traffic to internal services -- Helm values configuration included - -#### **Shared Database** (`shared-db/`) -- Centralized PostgreSQL database instance -- Shared across multiple applications -- Persistent volume and claim configuration -- NodePort service for external access - -### Applications - -#### **Bitwarden** (`bitwarden/`) -- Password manager and secrets vault -- Full Helm chart with templates and customizable values -- Persistent storage configuration - -#### **Vaultwarden** (`vaultwarden/`) -- Open-source Bitwarden alternative -- Complete Helm chart with deployment templates -- Ingress, service, and persistence configuration - -#### **Gitea** (`gitea/`) -- Git hosting service -- Persistent volume and PostgreSQL backed -- Values configuration for customization - -#### **Nextcloud** (`nextcloud/`) -- File sync, sharing, and collaboration platform -- Separate persistent volumes for data and PostgreSQL -- Notification push service included -- Custom ingress configuration - -#### **Immich** (`immich/`) -- Photo and video backup service -- Sub-chart for PostgreSQL database management -- Master node persistent volume -- PostgreSQL and application storage - -#### **Linkwarden Stack** (`linkwarden-stack/`) -- Link management and bookmarking service -- Complete Helm chart with ConfigMap, deployment, and ingress -- Persistent storage configuration - -#### **Mumble** (`mumble/`) -- Voice communication and VoIP service -- Helm values for configuration - -#### **Letsencrypt** (`letsencrypt/`) -- Automated SSL certificate provisioning -- Integrations with ingress controllers - -### Observability & Monitoring - -#### **Observability Stack** (`observability/`) - -##### **Prometheus** (`observability/prometheus/`) -- Metrics collection and time-series database -- Custom storage class for performance -- Persistent volume configuration - -##### **Loki** (`observability/loki/`) -- Log aggregation system -- Companion to Prometheus -- Dedicated storage configuration - -##### **Grafana** (`observability/grafana/`) -- Metrics and logs visualization -- Loki backend for log exploration -- Dashboard and alerting capabilities - -##### **Alloy** (`observability/alloy/`) -- Telemetry collection agent -- Data collection for Prometheus and Loki - -## 🚀 Deployment - -Each service is configured as a Helm chart with: -- `values.yaml` - Configuration and customization -- `Chart.yaml` - Chart metadata (where applicable) -- `templates/` - Kubernetes resource templates -- Persistent volume (PV) and persistent volume claim (PVC) for stateful services - -### Quick Start - -```bash -# Add Helm repositories as needed -helm repo add -helm repo update - -# Deploy a service -helm install -f /values.yaml -n -``` - -## 📝 Storage Configuration - -All persistent services include: -- **pv-\*.yaml** - PersistentVolume definitions -- **pvc-\*.yaml** - PersistentVolumeClaim definitions -- Reference storage class configurations - -## 🔗 Ingress Routes - -Traefik handles ingress routing with: -- `ingress.yaml` templates in major services -- SSL termination via Letsencrypt -- Pretty hostname routing (e.g., `bitwarden.example.com`) - -## 📚 Additional Resources - -- [backup.md](backup.md) - Backup and recovery procedures -- Individual service notes in each subdirectory (notes.md, NOTES.md) diff --git a/vaultwarden/notes.md b/vaultwarden/README.md similarity index 100% rename from vaultwarden/notes.md rename to vaultwarden/README.md diff --git a/zot/NOTE.md b/zot/README.md similarity index 100% rename from zot/NOTE.md rename to zot/README.md