--help` for detailed usage.
diff --git a/charts/dot-ai-stack/.github/workflows/release.yaml b/charts/dot-ai-stack/.github/workflows/release.yaml
new file mode 100644
index 0000000..601a593
--- /dev/null
+++ b/charts/dot-ai-stack/.github/workflows/release.yaml
@@ -0,0 +1,95 @@
+name: Release Helm Chart
+
+on:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+ submodules: true
+
+ - name: Set up Helm
+ uses: azure/setup-helm@v4
+ with:
+ version: v3.14.0
+
+ - name: Bump chart version
+ id: bump
+ run: |
+ # Read current version
+ CURRENT_VERSION=$(grep '^version:' Chart.yaml | awk '{print $2}')
+ echo "Current version: $CURRENT_VERSION"
+
+ # Parse version components
+ MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1)
+ MINOR=$(echo $CURRENT_VERSION | cut -d. -f2)
+ PATCH=$(echo $CURRENT_VERSION | cut -d. -f3)
+
+ # Increment minor, reset patch
+ NEW_MINOR=$((MINOR + 1))
+ NEW_VERSION="${MAJOR}.${NEW_MINOR}.0"
+ echo "New version: $NEW_VERSION"
+
+ # Update Chart.yaml
+ sed -i "s/^version: .*/version: $NEW_VERSION/" Chart.yaml
+ sed -i "s/^appVersion: .*/appVersion: \"$NEW_VERSION\"/" Chart.yaml
+
+ echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
+
+ - name: Commit version bump
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add Chart.yaml
+ git commit -m "chore: bump chart version to ${{ steps.bump.outputs.version }} [skip ci]"
+ git push
+
+ - name: Login to GHCR
+ run: |
+ echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
+
+ - name: Update dependencies
+ run: helm dependency update
+
+ - name: Package chart
+ run: helm package .
+
+ - name: Push to GHCR
+ run: |
+ helm push dot-ai-stack-*.tgz oci://ghcr.io/vfarcic/dot-ai-stack/charts
+
+ - name: Check if docs changed
+ id: docs-check
+ run: |
+ if git rev-parse HEAD~2 >/dev/null 2>&1; then
+ # Check the commit before the version bump (HEAD~1) against HEAD~2
+ DOCS_CHANGED=$(git diff --name-only HEAD~2 HEAD~1 -- README.md docs/ | wc -l)
+ else
+ DOCS_CHANGED=1 # First commit or second commit, trigger rebuild
+ fi
+ if [ "$DOCS_CHANGED" -gt 0 ]; then
+ echo "docs-changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "docs-changed=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Trigger Website Rebuild
+ if: steps.docs-check.outputs.docs-changed == 'true'
+ uses: peter-evans/repository-dispatch@v3
+ with:
+ token: ${{ secrets.WEBSITE_DISPATCH_TOKEN }}
+ repository: vfarcic/dot-ai-website
+ event-type: upstream-release
+ client-payload: '{"source": "dot-ai-stack"}'
diff --git a/charts/dot-ai-stack/.helmignore b/charts/dot-ai-stack/.helmignore
new file mode 100644
index 0000000..94a58cd
--- /dev/null
+++ b/charts/dot-ai-stack/.helmignore
@@ -0,0 +1,30 @@
+# Patterns to ignore when building packages.
+.DS_Store
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+.project
+.idea/
+*.tmproj
+.vscode/
+
+# Devbox
+.devbox/
+devbox.json
+devbox.lock
+
+# PRDs
+prds/
+
+# Environment files
+.env*
+kubeconfig.yaml
diff --git a/charts/dot-ai-stack/.mcp.json b/charts/dot-ai-stack/.mcp.json
new file mode 100644
index 0000000..472cb52
--- /dev/null
+++ b/charts/dot-ai-stack/.mcp.json
@@ -0,0 +1,11 @@
+{
+ "mcpServers": {
+ "coderabbitai": {
+ "command": "npx",
+ "args": ["coderabbitai-mcp@latest"],
+ "env": {
+ "GITHUB_PAT": "${GITHUB_TOKEN}"
+ }
+ }
+ }
+}
diff --git a/charts/dot-ai-stack/Chart.lock b/charts/dot-ai-stack/Chart.lock
new file mode 100644
index 0000000..cd4040a
--- /dev/null
+++ b/charts/dot-ai-stack/Chart.lock
@@ -0,0 +1,12 @@
+dependencies:
+- name: dot-ai
+ repository: oci://ghcr.io/vfarcic/dot-ai/charts
+ version: 1.12.0
+- name: dot-ai-controller
+ repository: oci://ghcr.io/vfarcic/dot-ai-controller/charts
+ version: 0.48.1
+- name: dot-ai-ui
+ repository: oci://ghcr.io/vfarcic/dot-ai-ui/charts
+ version: 0.15.0
+digest: sha256:2a839c436806060f27d64d9a49c7cb66193cebd8077f73d8a053e4e43300f772
+generated: "2026-03-21T16:18:35.4252363Z"
diff --git a/charts/dot-ai-stack/Chart.yaml b/charts/dot-ai-stack/Chart.yaml
new file mode 100644
index 0000000..1785cf1
--- /dev/null
+++ b/charts/dot-ai-stack/Chart.yaml
@@ -0,0 +1,18 @@
+apiVersion: v2
+appVersion: 0.78.0
+dependencies:
+- name: dot-ai
+ repository: oci://ghcr.io/vfarcic/dot-ai/charts
+ version: 1.12.0
+- condition: dot-ai-controller.enabled
+ name: dot-ai-controller
+ repository: oci://ghcr.io/vfarcic/dot-ai-controller/charts
+ version: 0.48.1
+- condition: dot-ai-ui.enabled
+ name: dot-ai-ui
+ repository: oci://ghcr.io/vfarcic/dot-ai-ui/charts
+ version: 0.15.0
+description: Complete dot-ai stack - MCP server, controller, and UI
+name: dot-ai-stack
+type: application
+version: 0.78.0
diff --git a/charts/dot-ai-stack/LICENSE b/charts/dot-ai-stack/LICENSE
new file mode 100644
index 0000000..adf495c
--- /dev/null
+++ b/charts/dot-ai-stack/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 Viktor Farcic
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/charts/dot-ai-stack/README.md b/charts/dot-ai-stack/README.md
new file mode 100644
index 0000000..5eab3d3
--- /dev/null
+++ b/charts/dot-ai-stack/README.md
@@ -0,0 +1,41 @@
+# dot-ai-stack
+
+[](LICENSE)
+
+
+**Deploy the complete DevOps AI Toolkit stack with a single Helm command.**
+
+---
+
+
+
+## [Read the Documentation](https://devopstoolkit.ai/docs/stack/)
+
+
+
+---
+
+## Overview
+
+dot-ai-stack is an umbrella Helm chart that aggregates all DevOps AI Toolkit components for simplified deployment. Instead of installing multiple charts separately, deploy everything with one command.
+
+**What's included:**
+- **DevOps AI Toolkit** - MCP server for AI-powered Kubernetes operations
+- **DevOps AI Toolkit Controller** - Kubernetes controller for intelligent resource management and autonomous operations
+- **DevOps AI Toolkit Web UI** - Web interface for visual cluster management
+- **Qdrant** - Vector database for pattern and policy storage
+- **ResourceSyncConfig** - Pre-configured resource discovery
+- **CapabilityScanConfig** - Pre-configured capability scanning
+
+[Read the Documentation](https://devopstoolkit.ai/docs/stack/)
+
+## Documentation
+
+- **[Setup Guide](https://devopstoolkit.ai/docs/stack/)** - Get up and running in minutes
+- **[DevOps AI Toolkit Docs](https://devopstoolkit.ai/docs/mcp/)** - MCP server documentation
+- **[Controller Docs](https://devopstoolkit.ai/docs/controller/)** - Controller documentation
+- **[Web UI Docs](https://devopstoolkit.ai/docs/ui/)** - Web interface documentation
+
+## License
+
+MIT License - see [LICENSE](LICENSE) file for details.
diff --git a/charts/dot-ai-stack/changelog.d/.gitkeep b/charts/dot-ai-stack/changelog.d/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/charts/dot-ai-stack/changelog.d/2.feature.md b/charts/dot-ai-stack/changelog.d/2.feature.md
new file mode 100644
index 0000000..1f8a65a
--- /dev/null
+++ b/charts/dot-ai-stack/changelog.d/2.feature.md
@@ -0,0 +1,19 @@
+**Configurable Timing Parameters for Resource Sync and Capability Scan**
+
+Control how frequently dot-ai synchronizes resources and scans cluster capabilities. Previously, these operations used fixed timing intervals, which could be too frequent for large clusters or too slow for rapidly changing environments.
+
+ResourceSyncConfig now supports `debounceWindowSeconds` (default: 10s, max: 300s) to control how long to wait after a change before syncing, and `resyncIntervalMinutes` (default: 60m, max: 1440m) to set the interval between full resyncs. CapabilityScanConfig supports `debounceWindowSeconds` with the same defaults. Both parameters are optionalβomit them to use the defaults.
+
+Configure these values in your Helm values file:
+```yaml
+resourceSync:
+ enabled: true
+ debounceWindowSeconds: 30
+ resyncIntervalMinutes: 120
+
+capabilityScan:
+ enabled: true
+ debounceWindowSeconds: 30
+```
+
+See the [Resource Sync Guide](https://devopstoolkit.ai/docs/controller/resource-sync-guide) and [Capability Scan Guide](https://devopstoolkit.ai/docs/controller/capability-scan-guide) for more details.
diff --git a/charts/dot-ai-stack/changelog.d/3.bugfix.md b/charts/dot-ai-stack/changelog.d/3.bugfix.md
new file mode 100644
index 0000000..6ab4672
--- /dev/null
+++ b/charts/dot-ai-stack/changelog.d/3.bugfix.md
@@ -0,0 +1,5 @@
+**Optional Component Disabling Now Works Correctly**
+
+Setting `dot-ai-controller.enabled: false` or `dot-ai-ui.enabled: false` now properly prevents those components from being rendered. Previously, the Helm chart dependencies lacked `condition` fields, so these settings had no effect and all resources were always deployed.
+
+This fix resolves drift detection issues in GitOps tools like Flux and ArgoCD, which would report removed resources when users attempted to disable components that were previously deployed. Both components default to `enabled: true`, so existing deployments are unaffected.
diff --git a/charts/dot-ai-stack/charts/dot-ai-1.12.0.tgz b/charts/dot-ai-stack/charts/dot-ai-1.12.0.tgz
new file mode 100644
index 0000000..ff9c165
Binary files /dev/null and b/charts/dot-ai-stack/charts/dot-ai-1.12.0.tgz differ
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller-0.48.1.tgz b/charts/dot-ai-stack/charts/dot-ai-controller-0.48.1.tgz
new file mode 100644
index 0000000..71aac50
Binary files /dev/null and b/charts/dot-ai-stack/charts/dot-ai-controller-0.48.1.tgz differ
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/Chart.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/Chart.yaml
new file mode 100644
index 0000000..7296132
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/Chart.yaml
@@ -0,0 +1,22 @@
+annotations:
+ category: Infrastructure
+apiVersion: v2
+appVersion: v0.48.1
+description: A Kubernetes controller that watches cluster events and forwards them
+ to the dot-ai MCP remediate tool for AI-powered analysis and remediation
+home: https://github.com/vfarcic/dot-ai-controller
+keywords:
+- kubernetes
+- controller
+- events
+- remediation
+- mcp
+- ai
+maintainers:
+- name: Viktor Farcic
+ url: https://github.com/vfarcic
+name: dot-ai-controller
+sources:
+- https://github.com/vfarcic/dot-ai-controller
+type: application
+version: 0.48.1
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/NOTES.txt b/charts/dot-ai-stack/charts/dot-ai-controller/templates/NOTES.txt
new file mode 100644
index 0000000..4afd4f0
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/NOTES.txt
@@ -0,0 +1,24 @@
+π dot-ai-controller has been installed!
+
+π WHAT'S INSTALLED:
+ β’ RemediationPolicy CRD for configuring event watching
+ β’ Controller deployment ({{ include "dot-ai-controller.fullname" . }}-manager)
+ β’ RBAC permissions for event monitoring
+
+π NEXT STEPS:
+
+1. Verify the controller is running:
+ kubectl get pods -l app.kubernetes.io/name={{ include "dot-ai-controller.name" . }} -n {{ .Release.Namespace }}
+
+2. Check controller logs:
+ kubectl logs -l app.kubernetes.io/name={{ include "dot-ai-controller.name" . }} -n {{ .Release.Namespace }}
+
+3. Create a RemediationPolicy to start watching events - see repository for configuration examples
+
+4. Monitor RemediationPolicy status:
+ kubectl get remediationpolicies -o wide
+
+π DOCUMENTATION:
+ Repository: https://github.com/vfarcic/dot-ai-controller
+
+β‘ The controller will automatically start processing events that match your RemediationPolicy selectors!
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/_helpers.tpl b/charts/dot-ai-stack/charts/dot-ai-controller/templates/_helpers.tpl
new file mode 100644
index 0000000..f94fb17
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/_helpers.tpl
@@ -0,0 +1,86 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "dot-ai-controller.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "dot-ai-controller.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "dot-ai-controller.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "dot-ai-controller.labels" -}}
+helm.sh/chart: {{ include "dot-ai-controller.chart" . }}
+{{ include "dot-ai-controller.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "dot-ai-controller.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "dot-ai-controller.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "dot-ai-controller.serviceAccountName" -}}
+{{- printf "%s-manager" (include "dot-ai-controller.fullname" .) }}
+{{- end }}
+
+{{/*
+Create the name of the cluster role to use
+*/}}
+{{- define "dot-ai-controller.clusterRoleName" -}}
+{{- printf "%s-manager-role" (include "dot-ai-controller.fullname" .) }}
+{{- end }}
+
+{{/*
+Create the name of the cluster role binding to use
+*/}}
+{{- define "dot-ai-controller.clusterRoleBindingName" -}}
+{{- printf "%s-manager-rolebinding" (include "dot-ai-controller.fullname" .) }}
+{{- end }}
+
+{{/*
+Merge global annotations with resource-specific annotations.
+Resource-specific annotations take precedence over global annotations.
+Usage: include "dot-ai-controller.annotations" (dict "global" .Values.annotations "local" .Values.ingress.annotations)
+*/}}
+{{- define "dot-ai-controller.annotations" -}}
+{{- $global := .global | default dict -}}
+{{- $local := .local | default dict -}}
+{{- $merged := merge $local $global -}}
+{{- if $merged -}}
+{{- toYaml $merged -}}
+{{- end -}}
+{{- end -}}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/deployment.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/deployment.yaml
new file mode 100644
index 0000000..1a15a3b
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/deployment.yaml
@@ -0,0 +1,72 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "dot-ai-controller.fullname" . }}-manager
+ namespace: {{ .Release.Namespace }}
+ labels:
+ {{- include "dot-ai-controller.labels" . | nindent 4 }}
+ control-plane: controller-manager
+ {{- $annotations := include "dot-ai-controller.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ {{- include "dot-ai-controller.selectorLabels" . | nindent 6 }}
+ control-plane: controller-manager
+ template:
+ metadata:
+ {{- $podAnnotations := include "dot-ai-controller.annotations" (dict "global" .Values.annotations "local" (dict "kubectl.kubernetes.io/default-container" "manager")) -}}
+ {{- if $podAnnotations }}
+ annotations:
+ {{- $podAnnotations | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "dot-ai-controller.selectorLabels" . | nindent 8 }}
+ control-plane: controller-manager
+ spec:
+ securityContext:
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ containers:
+ - name: manager
+ image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ command:
+ - /manager
+ args:
+ - --leader-elect
+ - --health-probe-bind-address=:8081
+ securityContext:
+ readOnlyRootFilesystem: true
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop:
+ - "ALL"
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8081
+ initialDelaySeconds: 15
+ periodSeconds: 20
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: 8081
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+ volumeMounts:
+ - name: tmp-dir
+ mountPath: /tmp
+ volumes:
+ - name: tmp-dir
+ emptyDir: {}
+ serviceAccountName: {{ include "dot-ai-controller.serviceAccountName" . }}
+ terminationGracePeriodSeconds: 1260
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_capabilityscanconfigs.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_capabilityscanconfigs.yaml
new file mode 100644
index 0000000..e5abf74
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_capabilityscanconfigs.yaml
@@ -0,0 +1,224 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: capabilityscanconfigs.dot-ai.devopstoolkit.live
+spec:
+ group: dot-ai.devopstoolkit.live
+ names:
+ kind: CapabilityScanConfig
+ listKind: CapabilityScanConfigList
+ plural: capabilityscanconfigs
+ singular: capabilityscanconfig
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Ready status
+ jsonPath: .status.conditions[?(@.type=='Ready')].status
+ name: Ready
+ type: string
+ - description: Initial scan completed
+ jsonPath: .status.initialScanComplete
+ name: Initial Scan
+ type: boolean
+ - description: Last scan time
+ jsonPath: .status.lastScanTime
+ name: Last Scan
+ type: date
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: |-
+ CapabilityScanConfig is the Schema for the capabilityscanconfigs API
+ It configures the controller to watch CRDs and trigger capability scans via MCP
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: spec defines the desired state of CapabilityScanConfig
+ properties:
+ debounceWindowSeconds:
+ default: 10
+ description: |-
+ DebounceWindowSeconds is the time window to collect CRD events before sending to MCP
+ When a CRD event is received, the controller waits for this duration to collect
+ more events, then sends them all in a single batched request.
+ This reduces HTTP requests when operators are installed (many CRDs at once).
+ maximum: 300
+ minimum: 1
+ type: integer
+ excludeResources:
+ description: |-
+ ExcludeResources specifies patterns for resources to exclude from scanning
+ Applied after includeResources filtering
+ Patterns support wildcards: "*.internal.example.com", "events.*"
+ items:
+ type: string
+ type: array
+ includeResources:
+ description: |-
+ IncludeResources specifies patterns for resources to include in scanning
+ Patterns support wildcards: "*.crossplane.io", "deployments.apps", "Service"
+ Format: "Kind.group" for grouped resources, "Kind" for core resources
+ If empty, all resources are included (subject to excludeResources)
+ items:
+ type: string
+ type: array
+ mcp:
+ description: MCP configuration for capability scanning
+ properties:
+ authSecretRef:
+ description: |-
+ AuthSecretRef references a Kubernetes Secret containing the MCP authentication token
+ The Secret must exist in the same namespace as the CapabilityScanConfig
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace as the
+ resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ collection:
+ default: capabilities
+ description: Collection is the Qdrant collection name for storing
+ capabilities
+ type: string
+ endpoint:
+ description: Endpoint is the MCP server URL
+ type: string
+ required:
+ - authSecretRef
+ - endpoint
+ type: object
+ retry:
+ description: Retry configuration for MCP API calls
+ properties:
+ backoffSeconds:
+ default: 5
+ description: |-
+ BackoffSeconds is the initial backoff duration in seconds
+ Subsequent retries use exponential backoff (backoff * 2^attempt)
+ maximum: 300
+ minimum: 1
+ type: integer
+ maxAttempts:
+ default: 3
+ description: MaxAttempts is the maximum number of retry attempts
+ (including initial attempt)
+ maximum: 10
+ minimum: 1
+ type: integer
+ maxBackoffSeconds:
+ default: 300
+ description: MaxBackoffSeconds is the maximum backoff duration
+ in seconds
+ maximum: 3600
+ minimum: 1
+ type: integer
+ type: object
+ required:
+ - mcp
+ type: object
+ status:
+ description: status defines the observed state of CapabilityScanConfig
+ properties:
+ conditions:
+ description: Current conditions of the config
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ initialScanComplete:
+ description: Whether the initial scan has been completed
+ type: boolean
+ lastError:
+ description: Last error message if any
+ type: string
+ lastScanTime:
+ description: Timestamp of last successful scan trigger
+ format: date-time
+ type: string
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_gitknowledgesources.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_gitknowledgesources.yaml
new file mode 100644
index 0000000..d731ac7
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_gitknowledgesources.yaml
@@ -0,0 +1,326 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: gitknowledgesources.dot-ai.devopstoolkit.live
+spec:
+ group: dot-ai.devopstoolkit.live
+ names:
+ kind: GitKnowledgeSource
+ listKind: GitKnowledgeSourceList
+ plural: gitknowledgesources
+ shortNames:
+ - gks
+ singular: gitknowledgesource
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Current sync phase
+ jsonPath: .status.phase
+ name: Phase
+ type: string
+ - description: Whether sync is active
+ jsonPath: .status.active
+ name: Active
+ type: boolean
+ - description: Number of synced documents
+ jsonPath: .status.documentCount
+ name: Documents
+ type: integer
+ - description: Time of last sync
+ jsonPath: .status.lastSyncTime
+ name: Last Sync
+ type: date
+ - description: Sync error count
+ jsonPath: .status.syncErrors
+ name: Errors
+ type: integer
+ - description: Time since creation
+ jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: |-
+ GitKnowledgeSource is the Schema for the gitknowledgesources API
+ It defines a Git repository to sync documents from into the MCP knowledge base
+ Works with any Git provider: GitHub, GitLab, Bitbucket, Gitea, self-hosted
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: GitKnowledgeSourceSpec defines the desired state of GitKnowledgeSource
+ properties:
+ deletionPolicy:
+ default: Delete
+ description: |-
+ DeletionPolicy specifies what happens to ingested documents when this CR is deleted.
+ Delete (default): Documents are removed from MCP knowledge base.
+ Retain: Documents remain in MCP knowledge base.
+ enum:
+ - Delete
+ - Retain
+ type: string
+ exclude:
+ description: |-
+ Exclude specifies glob patterns for files to exclude
+ Example: ["docs/internal/**"]
+ items:
+ type: string
+ type: array
+ maxFileSizeBytes:
+ description: |-
+ MaxFileSizeBytes limits the maximum file size to process
+ Files larger than this are skipped and reported in status
+ format: int64
+ type: integer
+ mcpServer:
+ description: McpServer configures the MCP server endpoint for knowledge
+ ingestion
+ properties:
+ authSecretRef:
+ description: AuthSecretRef references a Secret containing the
+ MCP authentication token
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace as the
+ resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ httpTimeoutSeconds:
+ default: 120
+ description: |-
+ HttpTimeoutSeconds is the HTTP timeout in seconds for MCP API calls
+ Increase this if syncing large documents that take longer to process
+ maximum: 600
+ minimum: 5
+ type: integer
+ url:
+ description: |-
+ URL is the MCP server endpoint
+ Example: "http://mcp-server.dot-ai.svc:3456"
+ pattern: ^https?://.*
+ type: string
+ required:
+ - authSecretRef
+ - url
+ type: object
+ metadata:
+ additionalProperties:
+ type: string
+ description: Metadata contains key-value pairs attached to all ingested
+ documents
+ type: object
+ paths:
+ description: |-
+ Paths specifies glob patterns for files to include
+ Example: ["docs/**/*.md", "README.md"]
+ items:
+ type: string
+ minItems: 1
+ type: array
+ repository:
+ description: Repository defines the Git repository to sync documents
+ from
+ properties:
+ branch:
+ default: main
+ description: Branch is the Git branch to sync from
+ type: string
+ depth:
+ default: 1
+ description: |-
+ Depth is the shallow clone depth for the initial sync
+ Subsequent syncs use --shallow-since to fetch only recent commits
+ minimum: 1
+ type: integer
+ secretRef:
+ description: |-
+ SecretRef references a Secret containing the Git authentication token
+ The token should be in the specified key within the Secret
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace as the
+ resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ url:
+ description: |-
+ URL is the Git repository URL (HTTPS only)
+ Example: "https://github.com/acme/platform.git"
+ pattern: ^https://.*
+ type: string
+ required:
+ - url
+ type: object
+ schedule:
+ default: '@every 24h'
+ description: |-
+ Schedule specifies when to sync using cron syntax or interval format
+ Supports standard cron (e.g., "0 3 * * *") or intervals (e.g., "@every 24h")
+ Default: "@every 24h" (once per day, staggered based on CR creation time)
+ type: string
+ required:
+ - mcpServer
+ - paths
+ - repository
+ type: object
+ status:
+ description: GitKnowledgeSourceStatus defines the observed state of GitKnowledgeSource
+ properties:
+ active:
+ description: Active indicates whether the controller is actively syncing
+ this source
+ type: boolean
+ conditions:
+ description: Conditions represent the latest available observations
+ of the GitKnowledgeSource's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ x-kubernetes-list-map-keys:
+ - type
+ x-kubernetes-list-type: map
+ documentCount:
+ description: DocumentCount is the number of documents currently synced
+ type: integer
+ lastError:
+ description: LastError contains the most recent error message
+ type: string
+ lastSyncTime:
+ description: LastSyncTime is the timestamp of the last successful
+ sync
+ format: date-time
+ type: string
+ lastSyncedCommit:
+ description: LastSyncedCommit is the Git commit SHA of the last successful
+ sync
+ type: string
+ nextScheduledSync:
+ description: NextScheduledSync is the timestamp of the next scheduled
+ sync
+ format: date-time
+ type: string
+ observedGeneration:
+ description: ObservedGeneration reflects the generation most recently
+ observed by the controller
+ format: int64
+ type: integer
+ phase:
+ description: Phase indicates the current sync phase (Syncing, Synced,
+ Error)
+ type: string
+ skippedDocuments:
+ description: SkippedDocuments is the count of documents skipped due
+ to filters
+ type: integer
+ skippedFiles:
+ description: SkippedFiles lists files that were skipped with reasons
+ items:
+ description: |-
+ SkippedFile represents a file or document that was skipped during sync
+ Used by knowledge source CRDs to report skipped items in status
+ properties:
+ path:
+ description: Path is the file path or identifier relative to
+ the source
+ type: string
+ reason:
+ description: Reason explains why the file was skipped
+ type: string
+ required:
+ - path
+ - reason
+ type: object
+ type: array
+ syncErrors:
+ description: SyncErrors is the count of errors in the last sync
+ type: integer
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_remediationpolicies.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_remediationpolicies.yaml
new file mode 100644
index 0000000..d523055
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_remediationpolicies.yaml
@@ -0,0 +1,377 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: remediationpolicies.dot-ai.devopstoolkit.live
+spec:
+ group: dot-ai.devopstoolkit.live
+ names:
+ kind: RemediationPolicy
+ listKind: RemediationPolicyList
+ plural: remediationpolicies
+ singular: remediationpolicy
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Whether the policy is ready
+ jsonPath: .status.conditions[?(@.type=='Ready')].status
+ name: Ready
+ type: string
+ - description: Total events processed
+ jsonPath: .status.totalEventsProcessed
+ name: Events
+ type: integer
+ - description: Successful remediations
+ jsonPath: .status.successfulRemediations
+ name: Successful
+ type: integer
+ - description: Failed remediations
+ jsonPath: .status.failedRemediations
+ name: Failed
+ type: integer
+ - description: Remediation mode
+ jsonPath: .spec.mode
+ name: Mode
+ type: string
+ - description: Number of event selectors
+ jsonPath: .spec.eventSelectors
+ name: Selectors
+ priority: 1
+ type: string
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: RemediationPolicy is the Schema for the remediationpolicies API
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: spec defines the desired state of RemediationPolicy
+ properties:
+ confidenceThreshold:
+ default: 0.8
+ description: Minimum confidence required for automatic execution (0.0-1.0)
+ maximum: 1
+ minimum: 0
+ type: number
+ eventSelectors:
+ description: Event selection criteria
+ items:
+ description: EventSelector defines criteria for selecting Kubernetes
+ events
+ properties:
+ confidenceThreshold:
+ description: |-
+ Minimum confidence required for automatic execution (0.0-1.0)
+ Overrides the global policy confidenceThreshold when specified
+ maximum: 1
+ minimum: 0
+ type: number
+ involvedObjectKind:
+ description: Kind of the involved object
+ type: string
+ maxRiskLevel:
+ description: |-
+ Maximum risk level allowed for automatic execution
+ Overrides the global policy maxRiskLevel when specified
+ enum:
+ - low
+ - medium
+ - high
+ type: string
+ message:
+ description: |-
+ Message pattern to match against event message (supports regex)
+ If specified, only events whose message matches this regex pattern will be selected
+ Empty string acts as wildcard (matches all messages)
+ type: string
+ mode:
+ description: |-
+ Remediation mode for this specific selector: "manual" or "automatic"
+ Overrides the global policy mode when specified
+ enum:
+ - manual
+ - automatic
+ type: string
+ namespace:
+ description: Namespace selector
+ type: string
+ reason:
+ description: Reason for the event
+ type: string
+ type:
+ description: Type of event (Warning, Normal)
+ type: string
+ type: object
+ type: array
+ maxRiskLevel:
+ default: low
+ description: Maximum risk level allowed for automatic execution
+ enum:
+ - low
+ - medium
+ - high
+ type: string
+ mcpAuthSecretRef:
+ description: |-
+ McpAuthSecretRef references a Kubernetes Secret containing the MCP authentication token
+ The controller will include "Authorization: Bearer " header in MCP requests
+ The Secret must exist in the same namespace as the RemediationPolicy
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace as the resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ mcpEndpoint:
+ description: MCP endpoint URL
+ type: string
+ mcpTool:
+ default: remediate
+ description: MCP tool name (always "remediate")
+ type: string
+ mode:
+ default: manual
+ description: 'Remediation mode: "manual" or "automatic"'
+ enum:
+ - manual
+ - automatic
+ type: string
+ notifications:
+ description: Notification configuration
+ properties:
+ googleChat:
+ description: Google Chat notification configuration
+ properties:
+ enabled:
+ default: false
+ description: Enable Google Chat notifications
+ type: boolean
+ notifyOnComplete:
+ default: true
+ description: Notify when remediation completes (default true)
+ type: boolean
+ notifyOnStart:
+ default: false
+ description: Notify when remediation starts (optional, default
+ false)
+ type: boolean
+ webhookUrl:
+ description: |-
+ WebhookUrl - DEPRECATED: Use webhookUrlSecretRef instead
+ Plain text webhook URL (discouraged for security reasons)
+ Must start with https://chat.googleapis.com/
+ type: string
+ webhookUrlSecretRef:
+ description: |-
+ WebhookUrlSecretRef - Kubernetes Secret reference (recommended)
+ References a Secret in the same namespace as the RemediationPolicy
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace
+ as the resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ type: object
+ slack:
+ description: Slack notification configuration
+ properties:
+ channel:
+ description: Slack channel (for display purposes only)
+ type: string
+ enabled:
+ default: false
+ description: Enable Slack notifications
+ type: boolean
+ notifyOnComplete:
+ default: true
+ description: Notify when remediation completes (default true)
+ type: boolean
+ notifyOnStart:
+ default: false
+ description: Notify when remediation starts (optional, default
+ false)
+ type: boolean
+ webhookUrl:
+ description: |-
+ WebhookUrl - DEPRECATED: Use webhookUrlSecretRef instead
+ Plain text webhook URL (discouraged for security reasons)
+ type: string
+ webhookUrlSecretRef:
+ description: |-
+ WebhookUrlSecretRef - Kubernetes Secret reference (recommended)
+ References a Secret in the same namespace as the RemediationPolicy
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace
+ as the resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ type: object
+ type: object
+ persistence:
+ description: |-
+ Persistence configuration for cooldown state
+ When not specified, persistence is enabled by default
+ properties:
+ enabled:
+ default: true
+ description: |-
+ Enable cooldown state persistence for this policy
+ When enabled, cooldown state survives pod restarts via ConfigMap storage
+ type: boolean
+ type: object
+ rateLimiting:
+ description: Rate limiting configuration
+ properties:
+ cooldownMinutes:
+ default: 5
+ description: Cooldown period in minutes after processing
+ type: integer
+ eventsPerMinute:
+ default: 10
+ description: Maximum events per minute
+ type: integer
+ type: object
+ required:
+ - eventSelectors
+ - mcpAuthSecretRef
+ - mcpEndpoint
+ type: object
+ status:
+ description: status defines the observed state of RemediationPolicy
+ properties:
+ conditions:
+ description: Current conditions of the policy
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ failedRemediations:
+ description: Number of failed remediation calls
+ format: int64
+ type: integer
+ lastMcpMessageGenerated:
+ description: Timestamp of last MCP message generated
+ format: date-time
+ type: string
+ lastProcessedEvent:
+ description: Timestamp of last processed event
+ format: date-time
+ type: string
+ lastRateLimitedEvent:
+ description: Timestamp of last rate limited event
+ format: date-time
+ type: string
+ rateLimitedEvents:
+ description: Number of events that were rate limited
+ format: int64
+ type: integer
+ successfulRemediations:
+ description: Number of successful remediation calls
+ format: int64
+ type: integer
+ totalEventsProcessed:
+ description: Total number of events processed
+ format: int64
+ type: integer
+ totalMcpMessagesGenerated:
+ description: Total number of MCP messages generated
+ format: int64
+ type: integer
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_resourcesyncconfigs.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_resourcesyncconfigs.yaml
new file mode 100644
index 0000000..62d6d2f
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_resourcesyncconfigs.yaml
@@ -0,0 +1,199 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: resourcesyncconfigs.dot-ai.devopstoolkit.live
+spec:
+ group: dot-ai.devopstoolkit.live
+ names:
+ kind: ResourceSyncConfig
+ listKind: ResourceSyncConfigList
+ plural: resourcesyncconfigs
+ singular: resourcesyncconfig
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Whether sync is active
+ jsonPath: .status.active
+ name: Active
+ type: boolean
+ - description: Resource types being watched
+ jsonPath: .status.watchedResourceTypes
+ name: Watched
+ type: integer
+ - description: Total resources synced
+ jsonPath: .status.totalResourcesSynced
+ name: Synced
+ type: integer
+ - description: Last sync time
+ jsonPath: .status.lastSyncTime
+ name: Last Sync
+ type: date
+ - description: Sync errors
+ jsonPath: .status.syncErrors
+ name: Errors
+ type: integer
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: |-
+ ResourceSyncConfig is the Schema for the resourcesyncconfigs API
+ It configures the controller to watch cluster resources and sync them to MCP
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: spec defines the desired state of ResourceSyncConfig
+ properties:
+ debounceWindowSeconds:
+ default: 10
+ description: |-
+ DebounceWindowSeconds is the time window to collect changes before sending to MCP
+ Multiple changes to the same resource within this window are batched together
+ maximum: 300
+ minimum: 1
+ type: integer
+ mcpAuthSecretRef:
+ description: |-
+ McpAuthSecretRef references a Kubernetes Secret containing the MCP authentication token
+ The controller will include "Authorization: Bearer " header in MCP requests
+ The Secret must exist in the same namespace as the ResourceSyncConfig
+ properties:
+ key:
+ description: Key within the secret containing the value
+ type: string
+ name:
+ description: Name of the secret in the same namespace as the resource
+ type: string
+ required:
+ - key
+ - name
+ type: object
+ mcpEndpoint:
+ description: MCP endpoint URL for resource sync
+ type: string
+ resyncIntervalMinutes:
+ default: 60
+ description: |-
+ ResyncIntervalMinutes is how often to perform a full resync with MCP
+ This ensures eventual consistency by reconciling any missed changes
+ maximum: 1440
+ minimum: 1
+ type: integer
+ required:
+ - mcpAuthSecretRef
+ - mcpEndpoint
+ type: object
+ status:
+ description: status defines the observed state of ResourceSyncConfig
+ properties:
+ active:
+ description: Whether resource syncing is currently active
+ type: boolean
+ conditions:
+ description: Current conditions of the config
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ lastError:
+ description: Last error message if any
+ type: string
+ lastResyncTime:
+ description: Timestamp of last full resync
+ format: date-time
+ type: string
+ lastSyncTime:
+ description: Timestamp of last successful sync to MCP
+ format: date-time
+ type: string
+ syncErrors:
+ description: Number of sync errors
+ format: int64
+ type: integer
+ totalResourcesSynced:
+ description: Total resources synced to MCP
+ format: int64
+ type: integer
+ watchedResourceTypes:
+ description: Number of resource types being watched
+ type: integer
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_solutions.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_solutions.yaml
new file mode 100644
index 0000000..f74b96a
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/dot-ai.devopstoolkit.live_solutions.yaml
@@ -0,0 +1,222 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.18.0
+ name: solutions.dot-ai.devopstoolkit.live
+spec:
+ group: dot-ai.devopstoolkit.live
+ names:
+ kind: Solution
+ listKind: SolutionList
+ plural: solutions
+ shortNames:
+ - sol
+ singular: solution
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: Original user intent
+ jsonPath: .spec.intent
+ name: Intent
+ type: string
+ - description: Solution state
+ jsonPath: .status.state
+ name: State
+ type: string
+ - description: Ready resources
+ jsonPath: .status.resources.ready
+ name: Resources
+ type: string
+ - description: Time since creation
+ jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: Solution is the Schema for the solutions API
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: SolutionSpec defines the desired state of Solution
+ properties:
+ context:
+ description: Context contains solution-level information not available
+ in individual resources
+ properties:
+ createdBy:
+ description: CreatedBy identifies the tool or user that created
+ this solution
+ type: string
+ patterns:
+ description: Patterns lists the organizational patterns applied
+ to this solution
+ items:
+ type: string
+ type: array
+ policies:
+ description: Policies lists the policies applied to this solution
+ items:
+ type: string
+ type: array
+ rationale:
+ description: Rationale explains why this solution was deployed
+ this way
+ type: string
+ type: object
+ documentationURL:
+ description: |-
+ DocumentationURL points to documentation for this solution
+ This field is populated by external tools (e.g., dot-ai PRD #228)
+ pattern: ^https?://.*
+ type: string
+ intent:
+ description: |-
+ Intent describes the original user intent that led to this deployment
+ Example: "Deploy Go microservice with PostgreSQL database and Redis cache"
+ minLength: 1
+ type: string
+ resources:
+ description: Resources lists all Kubernetes resources that compose
+ this solution
+ items:
+ description: ResourceReference identifies a Kubernetes resource
+ that is part of this solution
+ properties:
+ apiVersion:
+ description: APIVersion of the resource
+ type: string
+ kind:
+ description: Kind of the resource
+ type: string
+ name:
+ description: Name of the resource
+ type: string
+ namespace:
+ description: Namespace of the resource (if namespaced)
+ type: string
+ required:
+ - apiVersion
+ - kind
+ - name
+ type: object
+ minItems: 1
+ type: array
+ required:
+ - intent
+ - resources
+ type: object
+ status:
+ description: SolutionStatus defines the observed state of Solution
+ properties:
+ conditions:
+ description: Conditions represent the latest available observations
+ of the solution's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ x-kubernetes-list-map-keys:
+ - type
+ x-kubernetes-list-type: map
+ observedGeneration:
+ description: ObservedGeneration reflects the generation most recently
+ observed by the controller
+ format: int64
+ type: integer
+ resources:
+ description: Resources provides a summary of resource health
+ properties:
+ failed:
+ description: Failed number of resources that have failed
+ type: integer
+ ready:
+ description: Ready number of resources that are ready
+ type: integer
+ total:
+ description: Total number of resources in this solution
+ type: integer
+ type: object
+ state:
+ description: State represents the overall state of the solution
+ enum:
+ - pending
+ - deployed
+ - degraded
+ - failed
+ type: string
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/manager-role.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/manager-role.yaml
new file mode 100644
index 0000000..6c93f5c
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/manager-role.yaml
@@ -0,0 +1,113 @@
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ labels:
+ {{- include "dot-ai-controller.labels" . | nindent 4 }}
+ name: {{ include "dot-ai-controller.clusterRoleName" . }}
+rules:
+- apiGroups:
+ - ""
+ resources:
+ - configmaps
+ verbs:
+ - create
+- apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
+ - get
+ - list
+ - patch
+ - watch
+- apiGroups:
+ - ""
+ resources:
+ - pods
+ - secrets
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - '*'
+ resources:
+ - '*'
+ verbs:
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - apiextensions.k8s.io
+ resources:
+ - customresourcedefinitions
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - batch
+ resources:
+ - cronjobs
+ - jobs
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - create
+ - get
+ - list
+ - update
+- apiGroups:
+ - dot-ai.devopstoolkit.live
+ resources:
+ - capabilityscanconfigs
+ - gitknowledgesources
+ - resourcesyncconfigs
+ - solutions
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - dot-ai.devopstoolkit.live
+ resources:
+ - capabilityscanconfigs/finalizers
+ - gitknowledgesources/finalizers
+ - resourcesyncconfigs/finalizers
+ - solutions/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - dot-ai.devopstoolkit.live
+ resources:
+ - capabilityscanconfigs/status
+ - gitknowledgesources/status
+ - remediationpolicies/status
+ - resourcesyncconfigs/status
+ - solutions/status
+ verbs:
+ - get
+ - patch
+ - update
+- apiGroups:
+ - dot-ai.devopstoolkit.live
+ resources:
+ - remediationpolicies
+ verbs:
+ - get
+ - list
+ - watch
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/templates/rbac.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/templates/rbac.yaml
new file mode 100644
index 0000000..bcae462
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/templates/rbac.yaml
@@ -0,0 +1,33 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "dot-ai-controller.serviceAccountName" . }}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ {{- include "dot-ai-controller.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-controller.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{ include "dot-ai-controller.clusterRoleBindingName" . }}
+ labels:
+ {{- include "dot-ai-controller.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-controller.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ include "dot-ai-controller.clusterRoleName" . }}
+subjects:
+- kind: ServiceAccount
+ name: {{ include "dot-ai-controller.serviceAccountName" . }}
+ namespace: {{ .Release.Namespace }}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai-controller/values.yaml b/charts/dot-ai-stack/charts/dot-ai-controller/values.yaml
new file mode 100644
index 0000000..9bc98ec
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-controller/values.yaml
@@ -0,0 +1,25 @@
+# Default values for dot-ai-controller
+
+# Global annotations applied to ALL Kubernetes resources
+# Use for tools like Reloader, compliance requirements, or consistent metadata
+annotations: {}
+ # Example: Reloader integration
+ # reloader.stakater.com/auto: "true"
+ # Example: Compliance/audit
+ # company.com/managed-by: "platform-team"
+
+# Image configuration
+image:
+ repository: ghcr.io/vfarcic/dot-ai-controller
+ pullPolicy: IfNotPresent
+ # Overrides the image tag whose default is the chart appVersion
+ tag: ""
+
+# Resource limits and requests
+resources:
+ limits:
+ cpu: 500m
+ memory: 512Mi
+ requests:
+ cpu: 10m
+ memory: 128Mi
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui-0.15.0.tgz b/charts/dot-ai-stack/charts/dot-ai-ui-0.15.0.tgz
new file mode 100644
index 0000000..a0d44d5
Binary files /dev/null and b/charts/dot-ai-stack/charts/dot-ai-ui-0.15.0.tgz differ
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/Chart.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/Chart.yaml
new file mode 100644
index 0000000..3ce2548
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/Chart.yaml
@@ -0,0 +1,23 @@
+annotations:
+ category: Developer Tools
+apiVersion: v2
+appVersion: 0.15.0
+description: Web UI visualization companion for dot-ai MCP server - renders Mermaid
+ diagrams, cards, code blocks, and tables
+home: https://github.com/vfarcic/dot-ai-ui
+keywords:
+- devops
+- ai
+- kubernetes
+- visualization
+- mcp
+- mermaid
+- web-ui
+maintainers:
+- email: viktor@farcic.com
+ name: Viktor Farcic
+name: dot-ai-ui
+sources:
+- https://github.com/vfarcic/dot-ai-ui
+type: application
+version: 0.15.0
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/_helpers.tpl b/charts/dot-ai-stack/charts/dot-ai-ui/templates/_helpers.tpl
new file mode 100644
index 0000000..65de73d
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/_helpers.tpl
@@ -0,0 +1,101 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "dot-ai-ui.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "dot-ai-ui.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "dot-ai-ui.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "dot-ai-ui.labels" -}}
+helm.sh/chart: {{ include "dot-ai-ui.chart" . }}
+{{ include "dot-ai-ui.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "dot-ai-ui.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "dot-ai-ui.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the secret to use for auth token
+*/}}
+{{- define "dot-ai-ui.secretName" -}}
+{{- if .Values.dotAi.auth.secretRef.name }}
+{{- .Values.dotAi.auth.secretRef.name }}
+{{- else }}
+{{- include "dot-ai-ui.fullname" . }}-auth
+{{- end }}
+{{- end }}
+
+{{/*
+Create the key name for auth token in secret
+*/}}
+{{- define "dot-ai-ui.secretKey" -}}
+{{- default "auth-token" .Values.dotAi.auth.secretRef.key }}
+{{- end }}
+
+{{/*
+Create the name of the secret to use for UI auth token
+*/}}
+{{- define "dot-ai-ui.uiAuthSecretName" -}}
+{{- if .Values.uiAuth.secretRef.name }}
+{{- .Values.uiAuth.secretRef.name }}
+{{- else }}
+{{- include "dot-ai-ui.fullname" . }}-ui-auth
+{{- end }}
+{{- end }}
+
+{{/*
+Create the key name for UI auth token in secret
+*/}}
+{{- define "dot-ai-ui.uiAuthSecretKey" -}}
+{{- default "ui-auth-token" .Values.uiAuth.secretRef.key }}
+{{- end }}
+
+{{/*
+Merge global annotations with resource-specific annotations.
+Resource-specific annotations take precedence over global annotations.
+Usage: include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" .Values.ingress.annotations)
+*/}}
+{{- define "dot-ai-ui.annotations" -}}
+{{- $global := .global | default dict -}}
+{{- $local := .local | default dict -}}
+{{- $merged := merge $local $global -}}
+{{- if $merged -}}
+{{- toYaml $merged -}}
+{{- end -}}
+{{- end -}}
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/deployment.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/templates/deployment.yaml
new file mode 100644
index 0000000..aebf027
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/deployment.yaml
@@ -0,0 +1,75 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ {{- include "dot-ai-ui.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "dot-ai-ui.selectorLabels" . | nindent 8 }}
+ {{- $podAnnotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $podAnnotations }}
+ annotations:
+ {{- $podAnnotations | nindent 8 }}
+ {{- end }}
+ spec:
+ containers:
+ - name: web-ui
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
+ ports:
+ - containerPort: 3000
+ name: http
+ protocol: TCP
+ env:
+ - name: NODE_ENV
+ value: "production"
+ - name: PORT
+ value: "3000"
+ - name: DOT_AI_MCP_URL
+ value: {{ .Values.dotAi.url | quote }}
+ {{- if or .Values.dotAi.auth.secretRef.name .Values.dotAi.auth.token }}
+ - name: DOT_AI_AUTH_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: {{ include "dot-ai-ui.secretName" . }}
+ key: {{ include "dot-ai-ui.secretKey" . }}
+ optional: true
+ {{- end }}
+ {{- if or .Values.uiAuth.secretRef.name .Values.uiAuth.token }}
+ - name: DOT_AI_UI_AUTH_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: {{ include "dot-ai-ui.uiAuthSecretName" . }}
+ key: {{ include "dot-ai-ui.uiAuthSecretKey" . }}
+ optional: true
+ {{- end }}
+{{- with .Values.extraEnv }}
+ {{- toYaml . | nindent 8 }}
+{{- end }}
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+ livenessProbe:
+ httpGet:
+ path: /
+ port: 3000
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /
+ port: 3000
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ terminationGracePeriodSeconds: 30
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/gateway.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/templates/gateway.yaml
new file mode 100644
index 0000000..941a839
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/gateway.yaml
@@ -0,0 +1,52 @@
+{{- if .Values.gateway.create }}
+{{- if not .Values.gateway.className }}
+{{- fail "gateway.className is required when gateway.create is true" }}
+{{- end }}
+{{- if not (or .Values.gateway.listeners.http.enabled .Values.gateway.listeners.https.enabled) }}
+{{- fail "At least one listener (http or https) must be enabled when gateway.create is true" }}
+{{- end }}
+{{- if .Values.gateway.name }}
+{{- fail "gateway.name and gateway.create cannot both be set; disable gateway.create when using an existing gateway.name" }}
+{{- end }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}-http
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" .Values.gateway.annotations) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ gatewayClassName: {{ .Values.gateway.className }}
+ listeners:
+ {{- if .Values.gateway.listeners.http.enabled }}
+ - name: http
+ protocol: HTTP
+ port: 80
+ {{- if .Values.gateway.listeners.http.hostname }}
+ hostname: {{ .Values.gateway.listeners.http.hostname }}
+ {{- end }}
+ allowedRoutes:
+ namespaces:
+ from: Same
+ {{- end }}
+ {{- if .Values.gateway.listeners.https.enabled }}
+ - name: https
+ protocol: HTTPS
+ port: 443
+ {{- if .Values.gateway.listeners.https.hostname }}
+ hostname: {{ .Values.gateway.listeners.https.hostname }}
+ {{- end }}
+ tls:
+ mode: Terminate
+ certificateRefs:
+ - kind: Secret
+ name: {{ .Values.gateway.listeners.https.secretName | default (printf "%s-tls" (include "dot-ai-ui.fullname" .)) }}
+ allowedRoutes:
+ namespaces:
+ from: Same
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/httproute.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/templates/httproute.yaml
new file mode 100644
index 0000000..0967eed
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/httproute.yaml
@@ -0,0 +1,46 @@
+{{- if or .Values.gateway.name .Values.gateway.create }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ parentRefs:
+ - name: {{ if .Values.gateway.name }}{{ .Values.gateway.name }}{{ else }}{{ include "dot-ai-ui.fullname" . }}-http{{ end }}
+ kind: Gateway
+ {{- if and .Values.gateway.name .Values.gateway.namespace }}
+ namespace: {{ .Values.gateway.namespace }}
+ {{- end }}
+ {{- if or .Values.gateway.listeners.http.hostname .Values.gateway.listeners.https.hostname }}
+ hostnames:
+ {{- if .Values.gateway.listeners.http.hostname }}
+ - {{ .Values.gateway.listeners.http.hostname }}
+ {{- end }}
+ {{- if and .Values.gateway.listeners.https.hostname (ne .Values.gateway.listeners.http.hostname .Values.gateway.listeners.https.hostname) }}
+ - {{ .Values.gateway.listeners.https.hostname }}
+ {{- end }}
+ {{- end }}
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /
+ backendRefs:
+ - name: {{ include "dot-ai-ui.fullname" . }}
+ port: 3000
+ {{- if and .Values.gateway.timeouts (or .Values.gateway.timeouts.request .Values.gateway.timeouts.backendRequest) }}
+ timeouts:
+ {{- if .Values.gateway.timeouts.request }}
+ request: {{ .Values.gateway.timeouts.request }}
+ {{- end }}
+ {{- if .Values.gateway.timeouts.backendRequest }}
+ backendRequest: {{ .Values.gateway.timeouts.backendRequest }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/ingress.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/templates/ingress.yaml
new file mode 100644
index 0000000..108268a
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/ingress.yaml
@@ -0,0 +1,45 @@
+{{- if and .Values.ingress.enabled (or .Values.gateway.name .Values.gateway.create) }}
+{{- fail "Cannot enable both ingress.enabled and Gateway API usage (gateway.name or gateway.create). Please choose one ingress method." }}
+{{- end }}
+{{- if and .Values.ingress.tls.clusterIssuer (not .Values.ingress.tls.enabled) }}
+{{- fail "ingress.tls.clusterIssuer is set but ingress.tls.enabled is false. Enable TLS or remove clusterIssuer." }}
+{{- end }}
+{{- if .Values.ingress.enabled }}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" .Values.ingress.annotations) -}}
+ {{- if or $annotations .Values.ingress.tls.clusterIssuer }}
+ annotations:
+ {{- if $annotations }}
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+ {{- if .Values.ingress.tls.clusterIssuer }}
+ cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.clusterIssuer | quote }}
+ {{- end }}
+ {{- end }}
+spec:
+ {{- if .Values.ingress.className }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls.enabled }}
+ tls:
+ - hosts:
+ - {{ .Values.ingress.host }}
+ secretName: {{ .Values.ingress.tls.secretName | default (printf "%s-tls" (include "dot-ai-ui.fullname" .)) }}
+ {{- end }}
+ rules:
+ - host: {{ .Values.ingress.host }}
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: {{ include "dot-ai-ui.fullname" . }}
+ port:
+ number: 3000
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/secret.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/templates/secret.yaml
new file mode 100644
index 0000000..302a00c
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/secret.yaml
@@ -0,0 +1,33 @@
+{{- if and .Values.dotAi.auth.token (not .Values.dotAi.auth.secretRef.name) }}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}-auth
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+type: Opaque
+stringData:
+ auth-token: {{ .Values.dotAi.auth.token | quote }}
+{{- end }}
+---
+{{- if and .Values.uiAuth.token (not .Values.uiAuth.secretRef.name) }}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}-ui-auth
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+type: Opaque
+stringData:
+ ui-auth-token: {{ .Values.uiAuth.token | quote }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/templates/service.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/templates/service.yaml
new file mode 100644
index 0000000..393e0ff
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/templates/service.yaml
@@ -0,0 +1,20 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "dot-ai-ui.fullname" . }}
+ labels:
+ {{- include "dot-ai-ui.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai-ui.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ type: ClusterIP
+ selector:
+ {{- include "dot-ai-ui.selectorLabels" . | nindent 4 }}
+ ports:
+ - port: 3000
+ targetPort: 3000
+ protocol: TCP
+ name: http
diff --git a/charts/dot-ai-stack/charts/dot-ai-ui/values.yaml b/charts/dot-ai-stack/charts/dot-ai-ui/values.yaml
new file mode 100644
index 0000000..6e8951e
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai-ui/values.yaml
@@ -0,0 +1,110 @@
+# Global annotations applied to ALL Kubernetes resources
+# Use for tools like Reloader, compliance requirements, or consistent metadata
+annotations: {}
+ # Example: Reloader integration
+ # reloader.stakater.com/auto: "true"
+ # Example: Compliance/audit
+ # company.com/managed-by: "platform-team"
+
+# Application image configuration
+image:
+ repository: ghcr.io/vfarcic/dot-ai-ui # Container image repository
+ tag: "0.15.0" # Container image tag - set by CI pipeline during release
+ pullPolicy: IfNotPresent # Image pull policy
+
+# Resource configuration
+resources:
+ requests:
+ memory: "128Mi" # Minimum memory required
+ cpu: "50m" # Minimum CPU required
+ limits:
+ memory: "256Mi" # Maximum memory allowed
+ cpu: "200m" # Maximum CPU allowed
+
+# dot-ai MCP server connection
+dotAi:
+ # URL of the dot-ai MCP server API
+ # Required: The Web UI proxies visualization requests to this endpoint
+ url: "http://dot-ai:3456"
+
+ # Authentication configuration for MCP server
+ auth:
+ # Option 1: Reference existing secret (recommended for production)
+ secretRef:
+ name: "dot-ai-secrets" # Secret name (matches dot-ai MCP server default)
+ key: "auth-token" # Key within the secret
+
+ # Option 2: Let chart create secret (for development/testing)
+ token: "" # Auth token value (only if secretRef.name is empty)
+
+# UI Authentication - protects dashboard access
+# Required: Auth is always enabled; token auto-generated if not provided
+uiAuth:
+ # Option 1: Reference existing secret (recommended for production)
+ secretRef:
+ name: "" # Secret name containing UI auth token
+ key: "ui-auth-token" # Key within the secret
+
+ # Option 2: Provide token directly (for development/testing)
+ # If neither secretRef.name nor token is set, a random token is generated at startup
+ # (check pod logs for the generated token)
+ token: "" # UI auth token value
+
+# Ingress configuration
+# NOTE: Mutually exclusive with gateway - only one can be enabled
+ingress:
+ enabled: false # Create Ingress resource
+ className: nginx # Ingress class name
+ host: dot-ai-ui.127.0.0.1.nip.io # Ingress hostname
+ # Timeout annotations for long-running AI requests (query, remediate, operate, etc.)
+ # If using different className, update annotations for your ingress controller:
+ # - Traefik: traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true"
+ # - HAProxy: haproxy.org/timeout-http-request: "600s"
+ # - AWS ALB: alb.ingress.kubernetes.io/target-group-attributes: idle_timeout.timeout_seconds=600
+ annotations:
+ nginx.ingress.kubernetes.io/proxy-read-timeout: "600" # 10 min for AI responses
+ nginx.ingress.kubernetes.io/proxy-send-timeout: "600" # 10 min for AI responses
+ tls:
+ enabled: false # Enable TLS/HTTPS
+ secretName: "" # TLS secret name (generated if empty when enabled)
+ clusterIssuer: "" # cert-manager ClusterIssuer name (e.g., "letsencrypt")
+
+# Gateway API configuration (Kubernetes 1.26+)
+# NOTE: Mutually exclusive with ingress.enabled - only one can be enabled
+# Requires Gateway API CRDs pre-installed in cluster
+gateway:
+ # Reference mode: Specify name of existing Gateway resource (RECOMMENDED)
+ name: "" # Gateway name to reference (e.g., "cluster-gateway")
+
+ # Optional: Gateway namespace for cross-namespace references
+ namespace: "" # Gateway namespace (e.g., "gateway-system")
+
+ # Creation mode: Create a new Gateway resource (for development/testing only)
+ create: false # Create Gateway resource (false = reference mode)
+
+ # GatewayClass name - REQUIRED when create=true
+ className: "" # GatewayClass name (e.g., "istio", "envoy-gateway")
+
+ # Annotations for Gateway (only used when create=true)
+ annotations: {}
+
+ # HTTPRoute timeouts (Gateway API)
+ # Values are durations (e.g., "3600s", "60m", "1h")
+ timeouts:
+ request: "600s" # Max time for entire request (default: 10 min)
+ backendRequest: "600s" # Max time waiting for backend response (default: 10 min)
+
+ # Listener configuration - only used when create=true
+ listeners:
+ http:
+ enabled: true # Enable HTTP listener on port 80
+ hostname: "" # Optional: hostname for HTTP listener
+ https:
+ enabled: false # Enable HTTPS listener on port 443
+ hostname: "" # Optional: hostname for HTTPS listener
+ secretName: "" # TLS secret name
+
+# Additional environment variables (optional)
+extraEnv: []
+ # - name: NODE_ENV
+ # value: "production"
diff --git a/charts/dot-ai-stack/charts/dot-ai/Chart.lock b/charts/dot-ai-stack/charts/dot-ai/Chart.lock
new file mode 100644
index 0000000..4fcdfd1
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/Chart.lock
@@ -0,0 +1,9 @@
+dependencies:
+- name: qdrant
+ repository: https://qdrant.github.io/qdrant-helm
+ version: 1.15.5
+- name: dex
+ repository: https://charts.dexidp.io
+ version: 0.24.0
+digest: sha256:5e70a00728a97f1b2f129e41bb1a274280ac3c4bf1fcd5e98f18277ecb66048e
+generated: "2026-03-01T21:15:39.594662+01:00"
diff --git a/charts/dot-ai-stack/charts/dot-ai/Chart.yaml b/charts/dot-ai-stack/charts/dot-ai/Chart.yaml
new file mode 100644
index 0000000..8d29186
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/Chart.yaml
@@ -0,0 +1,31 @@
+annotations:
+ category: Developer Tools
+apiVersion: v2
+appVersion: 1.12.0
+dependencies:
+- condition: qdrant.enabled
+ name: qdrant
+ repository: https://qdrant.github.io/qdrant-helm
+ version: 1.15.5
+- condition: dex.enabled
+ name: dex
+ repository: https://charts.dexidp.io
+ version: 0.24.0
+description: DevOps AI Toolkit - Intelligent Kubernetes deployment agent with AI-powered
+ recommendations
+home: https://github.com/vfarcic/dot-ai
+keywords:
+- devops
+- ai
+- kubernetes
+- deployment
+- mcp
+- gateway-api
+maintainers:
+- email: viktor@farcic.com
+ name: Viktor Farcic
+name: dot-ai
+sources:
+- https://github.com/vfarcic/dot-ai
+type: application
+version: 1.12.0
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/.helmignore b/charts/dot-ai-stack/charts/dot-ai/charts/dex/.helmignore
new file mode 100644
index 0000000..00ca644
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/.helmignore
@@ -0,0 +1,25 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
+
+README.md.gotmpl
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/Chart.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/Chart.yaml
new file mode 100644
index 0000000..65b6162
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/Chart.yaml
@@ -0,0 +1,29 @@
+annotations:
+ artifacthub.io/changes: |
+ - kind: changed
+ description: "Update Dex to 2.44.0"
+ artifacthub.io/images: |
+ - name: dex
+ image: ghcr.io/dexidp/dex:v2.44.0
+apiVersion: v2
+appVersion: 2.44.0
+description: OpenID Connect (OIDC) identity and OAuth 2.0 provider with pluggable
+ connectors.
+home: https://dexidp.io/
+icon: https://dexidp.io/favicons/favicon.png
+keywords:
+- oidc
+- oauth
+- identity-provider
+- saml
+kubeVersion: '>=1.14.0-0'
+maintainers:
+- email: mark.sagikazar@gmail.com
+ name: sagikazarmark
+ url: https://sagikazarmark.com
+name: dex
+sources:
+- https://github.com/dexidp/dex
+- https://github.com/dexidp/helm-charts/tree/master/charts/dex
+type: application
+version: 0.24.0
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/LICENSE b/charts/dot-ai-stack/charts/dot-ai/charts/dex/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/README.md b/charts/dot-ai-stack/charts/dot-ai/charts/dex/README.md
new file mode 100644
index 0000000..552d6f0
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/README.md
@@ -0,0 +1,193 @@
+# dex
+
+    [](https://artifacthub.io/packages/helm/dex/dex)
+
+OpenID Connect (OIDC) identity and OAuth 2.0 provider with pluggable connectors.
+
+**Homepage:**
+
+## TL;DR;
+
+```bash
+helm repo add dex https://charts.dexidp.io
+helm install --generate-name --wait dex/dex
+```
+
+## Getting started
+
+### Minimal configuration
+
+Dex requires a minimal configuration in order to work.
+You can pass configuration to Dex using Helm values:
+
+```yaml
+config:
+ # Set it to a valid URL
+ issuer: http://my-issuer-url.com
+
+ # See https://dexidp.io/docs/storage/ for more options
+ storage:
+ type: memory
+
+ # Enable at least one connector
+ # See https://dexidp.io/docs/connectors/ for more options
+ enablePasswordDB: true
+```
+
+The above configuration won't make Dex automatically available on the configured URL.
+One (and probably the easiest) way to achieve that is configuring ingress:
+
+```yaml
+ingress:
+ enabled: true
+
+ hosts:
+ - host: my-issuer-url.com
+ paths:
+ - path: /
+```
+
+### Minimal TLS configuration
+
+HTTPS is basically mandatory these days, especially for authentication and authorization services.
+There are several solutions for protecting services with TlS in Kubernetes,
+but by far the most popular and portable is undoubtedly [Cert Manager](https://cert-manager.io).
+
+Cert Manager can be [installed](https://cert-manager.io/docs/installation/kubernetes) with a few steps:
+
+```shell
+helm repo add jetstack https://charts.jetstack.io
+helm repo update
+kubectl create namespace cert-manager
+helm install \
+ cert-manager jetstack/cert-manager \
+ --namespace cert-manager \
+ --set installCRDs=true
+```
+
+The next step is setting up an [issuer](https://cert-manager.io/docs/concepts/issuer/) (eg. [Let's Encrypt](https://letsencrypt.org/)):
+
+```shell
+cat <=1.23-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: autoscaling/v2
+{{- else -}}
+apiVersion: autoscaling/v2beta1
+{{- end }}
+kind: HorizontalPodAutoscaler
+metadata:
+ name: {{ include "dex.fullname" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: {{ include "dex.fullname" . }}
+ minReplicas: {{ .Values.autoscaling.minReplicas }}
+ maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+ metrics:
+ {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion }}
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- else }}
+ - type: Resource
+ resource:
+ name: cpu
+ targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- end }}
+ {{- end }}
+ {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- if semverCompare ">=1.23-0" .Capabilities.KubeVersion.GitVersion }}
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- else }}
+ - type: Resource
+ resource:
+ name: memory
+ targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/ingress.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/ingress.yaml
new file mode 100644
index 0000000..7eed9f5
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/ingress.yaml
@@ -0,0 +1,62 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "dex.fullname" . -}}
+{{- $svcPort := .Values.service.ports.http.port -}}
+{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
+ {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
+ {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
+ {{- end }}
+{{- end }}
+{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1
+{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+ name: {{ $fullName }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- range .Values.ingress.tls }}
+ - hosts:
+ {{- range .hosts }}
+ - {{ tpl . $ | quote }}
+ {{- end }}
+ secretName: {{ .secretName }}
+ {{- end }}
+ {{- end }}
+ rules:
+ {{- range .Values.ingress.hosts }}
+ - host: {{ tpl .host $ | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path }}
+ {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
+ pathType: {{ .pathType }}
+ {{- end }}
+ backend:
+ {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+ service:
+ name: {{ $fullName }}
+ port:
+ number: {{ $svcPort }}
+ {{- else }}
+ serviceName: {{ $fullName }}
+ servicePort: {{ $svcPort }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/networkpolicy.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/networkpolicy.yaml
new file mode 100644
index 0000000..521cd49
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/networkpolicy.yaml
@@ -0,0 +1,36 @@
+{{- if .Values.networkPolicy.enabled }}
+{{- if semverCompare "<1.7-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: extensions/v1beta1
+{{- else -}}
+apiVersion: networking.k8s.io/v1
+{{- end }}
+kind: NetworkPolicy
+metadata:
+ name: {{ include "dex.fullname" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+spec:
+ policyTypes:
+ {{- if .Values.networkPolicy.egressRules }}
+ - Egress
+ {{- end }}
+ - Ingress
+ podSelector:
+ matchLabels:
+ {{- include "dex.selectorLabels" . | nindent 6 }}
+ ingress:
+ - ports:
+ - port: http
+ {{- if .Values.https.enabled }}
+ - port: https
+ {{- end }}
+ {{- if .Values.grpc.enabled }}
+ - port: grpc
+ {{- end }}
+ - port: telemetry
+ {{- with .Values.networkPolicy.egressRules }}
+ egress:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/poddisruptionbudget.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/poddisruptionbudget.yaml
new file mode 100644
index 0000000..a091209
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/poddisruptionbudget.yaml
@@ -0,0 +1,23 @@
+{{- if .Values.podDisruptionBudget.enabled }}
+{{- if semverCompare ">=1.21-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: policy/v1
+{{- else -}}
+apiVersion: policy/v1beta1
+{{- end }}
+kind: PodDisruptionBudget
+metadata:
+ name: {{ template "dex.fullname" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+{{ include "dex.labels" . | indent 4 }}
+spec:
+ {{- with .Values.podDisruptionBudget.minAvailable }}
+ minAvailable: {{ . }}
+ {{- end }}
+ {{- with .Values.podDisruptionBudget.maxUnavailable }}
+ maxUnavailable: {{ . }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "dex.selectorLabels" . | nindent 6 }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/rbac.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/rbac.yaml
new file mode 100644
index 0000000..8f92361
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/rbac.yaml
@@ -0,0 +1,57 @@
+{{- if .Values.rbac.create }}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: {{ include "dex.fullname" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+rules:
+ - apiGroups: ["dex.coreos.com"]
+ resources: ["*"]
+ verbs: ["*"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: {{ include "dex.fullname" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+roleRef:
+ kind: Role
+ apiGroup: rbac.authorization.k8s.io
+ name: {{ include "dex.fullname" . }}
+subjects:
+- kind: ServiceAccount
+ namespace: {{ include "dex.namespace" . }}
+ name: {{ include "dex.serviceAccountName" . }}
+{{- if .Values.rbac.createClusterScoped }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ include "dex.fullname" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+rules:
+ - apiGroups: ["apiextensions.k8s.io"]
+ resources: ["customresourcedefinitions"]
+ verbs: ["list", "create"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{ include "dex.fullname" . }}-cluster
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+roleRef:
+ kind: ClusterRole
+ apiGroup: rbac.authorization.k8s.io
+ name: {{ include "dex.fullname" . }}
+subjects:
+- kind: ServiceAccount
+ namespace: {{ include "dex.namespace" . }}
+ name: {{ include "dex.serviceAccountName" . }}
+{{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/secret.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/secret.yaml
new file mode 100644
index 0000000..47d3d2b
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/secret.yaml
@@ -0,0 +1,12 @@
+{{- if .Values.configSecret.create -}}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "dex.configSecretName" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+type: Opaque
+data:
+ config.yaml: {{ .Values.config | toYaml | b64enc | quote }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/service.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/service.yaml
new file mode 100644
index 0000000..2a0122f
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/service.yaml
@@ -0,0 +1,65 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "dex.fullname" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+ {{- with .Values.service.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ type: {{ .Values.service.type }}
+ {{- with .Values.service.clusterIP }}
+ clusterIP: {{ . }}
+ {{- end }}
+ {{- if eq .Values.service.type "LoadBalancer" }}
+ {{- with .Values.service.loadBalancerIP }}
+ loadBalancerIP: {{ . }}
+ {{- end }}
+ {{- end }}
+ ports:
+ - name: http
+ port: {{ .Values.service.ports.http.port }}
+ {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.http.nodePort }}
+ nodePort: {{ .Values.service.ports.http.nodePort }}
+ {{- end }}
+ targetPort: http
+ protocol: TCP
+ {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }}
+ appProtocol: http
+ {{- end }}
+ {{- if .Values.https.enabled }}
+ - name: https
+ port: {{ .Values.service.ports.https.port }}
+ {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.https.nodePort }}
+ nodePort: {{ .Values.service.ports.https.nodePort }}
+ {{- end }}
+ targetPort: https
+ protocol: TCP
+ {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }}
+ appProtocol: https
+ {{- end }}
+ {{- end }}
+ {{- if .Values.grpc.enabled }}
+ - name: grpc
+ port: {{ .Values.service.ports.grpc.port }}
+ {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.grpc.nodePort }}
+ nodePort: {{ .Values.service.ports.grpc.nodePort }}
+ {{- end }}
+ targetPort: grpc
+ protocol: TCP
+ {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }}
+ appProtocol: http
+ {{- end }}
+ {{- end }}
+ - name: telemetry
+ port: 5558
+ targetPort: telemetry
+ protocol: TCP
+ {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }}
+ appProtocol: http
+ {{- end }}
+ selector:
+ {{- include "dex.selectorLabels" . | nindent 4 }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/serviceaccount.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/serviceaccount.yaml
new file mode 100644
index 0000000..5c6f88f
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/serviceaccount.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "dex.serviceAccountName" . }}
+ namespace: {{ include "dex.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+ {{- with .Values.serviceAccount.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/servicemonitor.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/servicemonitor.yaml
new file mode 100644
index 0000000..aa21985
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/servicemonitor.yaml
@@ -0,0 +1,52 @@
+{{- if .Values.serviceMonitor.enabled }}
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+ {{- with .Values.serviceMonitor.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ name: {{ include "dex.fullname" . }}
+ namespace: {{ include "dex.serviceMonitor.namespace" . }}
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+ {{- with .Values.serviceMonitor.labels }}
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ endpoints:
+ - port: telemetry
+ {{- with .Values.serviceMonitor.interval }}
+ interval: {{ . }}
+ {{- end }}
+ {{- with .Values.serviceMonitor.scheme }}
+ scheme: {{ . }}
+ {{- end }}
+ {{- with .Values.serviceMonitor.bearerTokenFile }}
+ bearerTokenFile: {{ . }}
+ {{- end }}
+ {{- with .Values.serviceMonitor.tlsConfig }}
+ tlsConfig:
+ {{- toYaml .| nindent 6 }}
+ {{- end }}
+ {{- with .Values.serviceMonitor.scrapeTimeout }}
+ scrapeTimeout: {{ . }}
+ {{- end }}
+ path: {{ .Values.serviceMonitor.path }}
+ honorLabels: {{ .Values.serviceMonitor.honorLabels }}
+ {{- with .Values.serviceMonitor.metricRelabelings }}
+ metricRelabelings:
+ {{- tpl (toYaml . | nindent 6) $ }}
+ {{- end }}
+ {{- with .Values.serviceMonitor.relabelings }}
+ relabelings:
+ {{- toYaml . | nindent 6 }}
+ {{- end }}
+ jobLabel: {{ include "dex.fullname" . }}
+ selector:
+ matchLabels:
+ {{- include "dex.selectorLabels" . | nindent 6 }}
+ namespaceSelector:
+ matchNames:
+ - {{ include "dex.namespace" . }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/tests/no-config-secret.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/tests/no-config-secret.yaml
new file mode 100644
index 0000000..4b7804f
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/templates/tests/no-config-secret.yaml
@@ -0,0 +1,13 @@
+{{- if not .Values.configSecret.create -}}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "dex.configSecretName" . }}-test-no-create
+ labels:
+ {{- include "dex.labels" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+type: Opaque
+data:
+ config.yaml: {{ .Values.config | toYaml | b64enc | quote }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/dex/values.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/dex/values.yaml
new file mode 100644
index 0000000..2515f6c
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/dex/values.yaml
@@ -0,0 +1,345 @@
+# Default values for dex.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+# -- Number of replicas (pods) to launch.
+replicaCount: 1
+
+# -- Labels to apply to all resources and selectors.
+commonLabels: {}
+# team_name: dev
+
+image:
+ # -- Name of the image repository to pull the container image from.
+ repository: ghcr.io/dexidp/dex
+
+ # -- [Image pull policy](https://kubernetes.io/docs/concepts/containers/images/#updating-images) for updating already existing images on a node.
+ pullPolicy: IfNotPresent
+
+ # -- Image tag override for the default value (chart appVersion).
+ tag: ""
+
+ # -- When digest is set to a non-empty value, images will be pulled by digest (regardless of tag value).
+ digest: ""
+
+# -- Reference to one or more secrets to be used when [pulling images](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-pod-that-uses-your-secret) (from private registries).
+imagePullSecrets: []
+
+# -- A namespace in place of the release namespace for all resources.
+namespaceOverride: ""
+
+# -- A name in place of the chart name for `app:` labels.
+nameOverride: ""
+
+# -- A name to substitute for the full names of resources.
+fullnameOverride: ""
+
+# -- A list of hosts and IPs that will be injected into the pod's hosts file if specified.
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#hostname-and-name-resolution)
+hostAliases: []
+
+https:
+ # -- Enable the HTTPS endpoint.
+ enabled: false
+
+grpc:
+ # -- Enable the gRPC endpoint.
+ # Read more in the [documentation](https://dexidp.io/docs/api/).
+ enabled: false
+
+configSecret:
+ # -- Enable creating a secret from the values passed to `config`.
+ # If set to false, name must point to an existing secret.
+ create: true
+
+ # -- The name of the secret to mount as configuration in the pod.
+ # If not set and create is true, a name is generated using the fullname template.
+ # Must point to secret that contains at least a `config.yaml` key.
+ name: ""
+
+# -- Application configuration.
+# See the [official documentation](https://dexidp.io/docs/).
+config: {}
+
+# -- Additional storage [volumes](https://kubernetes.io/docs/concepts/storage/volumes/).
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#volumes-1) for details.
+volumes: []
+
+# -- Additional [volume mounts](https://kubernetes.io/docs/tasks/configure-pod-container/configure-volume-storage/).
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#volumes-1) for details.
+volumeMounts: []
+
+# -- Additional environment variables mounted from [secrets](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-environment-variables) or [config maps](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables).
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables) for details.
+envFrom: []
+
+# -- Additional environment variables passed directly to containers.
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables) for details.
+env: {}
+
+# -- Similar to env but with support for all possible configurations.
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables) for details.
+envVars: []
+# - name: SOME_ENV_VAR
+# value: value
+# - name: SOME_ENV_VAR2
+# valueFrom:
+# secretKeyRef:
+# name: secret-name
+# key: secret-key
+# - name: SOME_ENV_VAR3
+# valueFrom:
+# configMapKeyRef:
+# name: config-map-name
+# key: config-map-key
+
+serviceAccount:
+ # -- Enable service account creation.
+ create: true
+
+ # -- Annotations to be added to the service account.
+ annotations: {}
+
+ # -- The name of the service account to use.
+ # If not set and create is true, a name is generated using the fullname template.
+ name: ""
+
+rbac:
+ # -- Specifies whether RBAC resources should be created.
+ # If disabled, the operator is responsible for creating the necessary resources based on the templates.
+ create: true
+
+ # -- Specifies which RBAC resources should be created.
+ # If disabled, the operator is responsible for creating the necessary resources (ClusterRole and RoleBinding or CRD's)
+ createClusterScoped: true
+
+# -- Annotations to be added to deployment.
+deploymentAnnotations: {}
+
+# -- Labels to be added to deployment.
+deploymentLabels: {}
+
+# -- Annotations to be added to pods.
+podAnnotations: {}
+
+# -- Labels to be added to pods.
+podLabels: {}
+
+podDisruptionBudget:
+ # -- Enable a [pod distruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) to help dealing with [disruptions](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/).
+ # It is **highly recommended** for webhooks as disruptions can prevent launching new pods.
+ enabled: false
+
+ # -- (int/percentage) Number or percentage of pods that must remain available.
+ minAvailable:
+
+ # -- (int/percentage) Number or percentage of pods that can be unavailable.
+ maxUnavailable:
+
+# -- Specify a priority class name to set [pod priority](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/#pod-priority).
+priorityClassName: ""
+
+# -- Pod [security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod).
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context) for details.
+podSecurityContext: {}
+ # fsGroup: 2000
+
+# -- Define the [count of deployment revisions](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#clean-up-policy) to be kept.
+# May be set to 0 in case of GitOps deployment approach.
+revisionHistoryLimit: 10
+
+# -- Container [security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container).
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#security-context-1) for details.
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+service:
+ # -- Annotations to be added to the service.
+ annotations: {}
+
+ # -- Kubernetes [service type](https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types).
+ type: ClusterIP
+
+ # -- Internal cluster service IP (when applicable)
+ clusterIP: ""
+
+ # -- Load balancer service IP (when applicable)
+ loadBalancerIP: ""
+
+ ports:
+ http:
+ # -- HTTP service port
+ port: 5556
+
+ # -- (int) HTTP node port (when applicable)
+ nodePort:
+
+ https:
+ # -- HTTPS service port
+ port: 5554
+
+ # -- (int) HTTPS node port (when applicable)
+ nodePort:
+
+ grpc:
+ # -- gRPC service port
+ port: 5557
+
+ # -- (int) gRPC node port (when applicable)
+ nodePort:
+
+ingress:
+ # -- Enable [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/).
+ enabled: false
+
+ # -- Ingress [class name](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class).
+ className: ""
+
+ # -- Annotations to be added to the ingress.
+ annotations: {}
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+
+ # -- Ingress host configuration.
+ # @default -- See [values.yaml](values.yaml).
+ hosts:
+ - host: chart-example.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+
+ # -- Ingress TLS configuration.
+ # @default -- See [values.yaml](values.yaml).
+ tls: []
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local
+
+serviceMonitor:
+ # -- Enable Prometheus ServiceMonitor.
+ # See the [documentation](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/design.md#servicemonitor) and the [API reference](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#servicemonitor) for details.
+ enabled: false
+
+ # -- Namespace where the ServiceMonitor resource should be deployed.
+ # @default -- Release namespace.
+ namespace: ""
+
+ # -- (duration) Prometheus scrape interval.
+ interval:
+
+ # -- (duration) Prometheus scrape timeout.
+ scrapeTimeout:
+
+ # -- Labels to be added to the ServiceMonitor.
+ ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
+ labels: {}
+
+ # -- Annotations to be added to the ServiceMonitor.
+ ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec
+ annotations: {}
+
+ # -- HTTP scheme to use for scraping.
+ # Can be used with `tlsConfig` for example if using istio mTLS.
+ scheme: ""
+
+ # -- HTTP path to scrape for metrics.
+ path: /metrics
+
+ # -- TLS configuration to use when scraping the endpoint.
+ # For example if using istio mTLS.
+ ## Of type: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#tlsconfig
+ tlsConfig: {}
+
+ # -- Prometheus scrape bearerTokenFile
+ bearerTokenFile:
+
+ # -- HonorLabels chooses the metric's labels on collisions with target labels.
+ honorLabels: false
+
+ # -- Prometheus scrape metric relabel configs
+ # to apply to samples before ingestion.
+ ## [Metric Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#metric_relabel_configs)
+ metricRelabelings: []
+ # - action: keep
+ # regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+'
+ # sourceLabels: [__name__]
+
+ # -- Relabel configs to apply
+ # to samples before ingestion.
+ ## [Relabeling](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config)
+ relabelings: []
+ # - sourceLabels: [__meta_kubernetes_pod_node_name]
+ # separator: ;
+ # regex: ^(.*)$
+ # targetLabel: nodename
+ # replacement: $1
+ # action: replace
+
+# -- Container resource [requests and limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources) for details.
+# @default -- No requests or limits.
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+# -- Autoscaling configuration (see [values.yaml](values.yaml) for details).
+# @default -- Disabled by default.
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 100
+ targetCPUUtilizationPercentage: 80
+ # targetMemoryUtilizationPercentage: 80
+
+# -- [Node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) configuration.
+nodeSelector: {}
+
+# -- [Tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for node taints.
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) for details.
+tolerations: []
+
+# -- [Affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) configuration.
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) for details.
+affinity: {}
+
+# -- [TopologySpreadConstraints](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/) configuration.
+# See the [API reference](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#scheduling) for details.
+topologySpreadConstraints: []
+
+# -- Deployment [strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) configuration.
+strategy: {}
+ # rollingUpdate:
+ # maxUnavailable: 1
+ # type: RollingUpdate
+
+networkPolicy:
+ # -- Create [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
+ enabled: false
+ # -- A list of network policy egress rules
+ egressRules: []
+ # Allow DNS egress traffic
+ # - ports:
+ # - port: 53
+ # protocol: UDP
+ # - port: 53
+ # protocol: TCP
+ # Example to allow LDAP connector to reach LDAPs port on 1.2.3.4 server
+ # - to:
+ # - ipBlock
+ # cidr: 1.2.3.4/32
+ # ports:
+ # - port: 636
+ # protocol: TCP
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/.helmignore b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/.helmignore
new file mode 100644
index 0000000..f4b1198
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/.helmignore
@@ -0,0 +1,2 @@
+.git
+.github
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/CHANGELOG.md b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/CHANGELOG.md
new file mode 100644
index 0000000..5fb624a
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/CHANGELOG.md
@@ -0,0 +1,6 @@
+# Changelog
+
+## [qdrant-1.15.5](https://github.com/qdrant/qdrant-helm/tree/qdrant-1.15.4) (2025-09-30)
+
+- Update Qdrant to v1.15.5
+
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/Chart.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/Chart.yaml
new file mode 100644
index 0000000..019e84f
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/Chart.yaml
@@ -0,0 +1,22 @@
+annotations:
+ artifacthub.io/category: database
+ artifacthub.io/changes: |
+ - kind: added
+ description: Update Qdrant to v1.15.5
+apiVersion: v2
+appVersion: v1.15.5
+description: Qdrant - Vector Database for the next generation of AI applications.
+home: https://qdrant.tech
+icon: https://qdrant.github.io/qdrant-helm/logo_with_text.svg
+keywords:
+- vector database
+maintainers:
+- email: info@qdrant.com
+ name: qdrant
+ url: https://github.com/qdrant
+name: qdrant
+sources:
+- https://github.com/qdrant/qdrant
+- https://github.com/qdrant/qdrant-helm
+type: application
+version: 1.15.5
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/LICENSE b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/LICENSE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/README.md b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/README.md
new file mode 100644
index 0000000..512eda1
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/README.md
@@ -0,0 +1,157 @@
+# Qdrant helm chart
+
+[Qdrant documentation](https://qdrant.tech/documentation/)
+
+## TLDR
+
+```bash
+helm repo add qdrant https://qdrant.github.io/qdrant-helm
+helm repo update
+helm upgrade -i your-qdrant-installation-name qdrant/qdrant
+```
+
+## Description
+
+This chart installs and bootstraps a Qdrant instance.
+
+## Prerequisites
+
+- Kubernetes v1.24+ (as you need grpc probe)
+- Helm
+- PV provisioner (by the infrastructure)
+
+## Installation & Setup
+
+You can install the chart from source via:
+
+```bash
+helm upgrade -i your-qdrant-installation-name charts/qdrant
+```
+
+Uninstall via:
+
+```bash
+helm uninstall your-qdrant-installation-name
+```
+
+Delete the volume with
+
+```bash
+kubectl delete pvc -l app.kubernetes.io/instance=your-qdrant-installation-name
+```
+
+## Configuration
+
+For documentation of the settings please refer to [Qdrant Configuration File](https://github.com/qdrant/qdrant/blob/master/config/config.yaml)
+All of these configuration options could be overwritten under config in `values.yaml`.
+A modification example is provided there.
+
+### Overrides
+
+You can override any value in the Qdrant configuration by setting the Helm values under the key `config`. Those settings get included verbatim in a file called `config/production.yml` which is explained further here [Qdrant Order and Priority](https://qdrant.tech/documentation/guides/configuration/#order-and-priority) as well as an [example](https://github.com/qdrant/qdrant-helm/blob/b0bb6fc6d3eb9c0813c79bb5a78dc21aebc2b81d/charts/qdrant/values.yaml#L140).
+
+### Distributed setup
+
+Running a distributed cluster just needs a few changes in your `values.yaml` file.
+Increase the number of replicas to the desired number of nodes and set `config.cluster.enabled` to true.
+
+Depending on your environment or cloud provider you might need to change the service in the `values.yaml` as well.
+For example on AWS EKS you would need to change the `cluster.type` to `NodePort`.
+
+## Updating StatefulSets
+
+This Helm chart uses a Kubernetes [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) to manage your Qdrant cluster. StatefulSets have many fields that are immutable, meaning that you cannot change these fields without deleting and recreating the StatefulSet. If you try to change these fields, you will get an error like this:
+
+```
+Error: UPGRADE FAILED: cannot patch "qdrant" with kind StatefulSet: StatefulSet.apps "qdrant" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden
+```
+
+If you need to change any immutable field, the process is described below, using the most common example of expanding a PVC volume.
+
+1. Delete the StatefulSet while leaving the Pods running:
+
+ ```bash
+ kubectl delete statefulset --cascade=orphan qdrant
+ ```
+
+2. Manually edit all PersistentVolumeClaims to increase their sizes:
+
+ ```bash
+ # For each PersistentVolumeClaim:
+ kubectl edit pvc qdrant-storage-qdrant-0
+ ```
+
+3. Update your Helm values to match the new PVC size.
+4. Reinstall the Helm chart using your updated values:
+
+ ```bash
+ helm upgrade --install qdrant qdrant/qdrant -f my-values.yaml
+ ```
+
+Some storage providers allow resizing volumes in-place, but most require a pod restart before the new size will take effect:
+
+```bash
+kubectl rollout restart statefulset qdrant
+```
+
+### Immutable Pod fields
+
+In addition to immutable fields on StatefulSets, Pods also have some fields which are immutable, which means the above method may not work for some changes, such as setting `snapshotPersistence.enabled: true`. In that case, after following the above method, you'll see an error like this when you `kubectl describe` your StatefulSet:
+
+```
+pod updates may not change fields other than `spec.containers[*].image`,
+`spec.initContainers[*].image`,`spec.activeDeadlineSeconds`,
+`spec.tolerations` (only additions to existing tolerations),
+`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative)
+```
+
+To fix this, you must manually delete all of your Qdrant pods, starting with node-0. This will cause your cluster to go down, but will allow the StatefulSet to recreate your Pods with the correct configuration.
+
+## Restoring from Snapshots
+
+This helm chart allows you to restore a snapshot into your Qdrant cluster either from an internal or external PersistentVolumeClaim.
+
+### Restoring from the built-in PVC
+
+If you have set `snapshotPersistence.enabled: true` (recommended for production), this helm chart will create a separate PersistentVolume for snapshots, and any snapshots you create will be stored in that PersistentVolume.
+
+To restore from one of these snapshots, set the following values:
+
+```yaml
+snapshotRestoration:
+ enabled: true
+ # Set blank to indicate we are not using an external PVC
+ pvcName: ""
+ snapshots:
+ - /qdrant/snapshots///:
+```
+
+And run "helm upgrade". This will restart your cluster and restore the specified collection from the snapshot. Qdrant will refuse to overwrite an existing collection, so ensure the collection is deleted before restoring.
+
+After the snapshot is restored, remove the above values and run "helm upgrade" again to trigger another rolling restart. Otherwise, the snapshot restore will be attempted again if your cluster ever restarts.
+
+### Restoring from an external PVC
+
+If you wish to restore from an externally-created snapshot, using the API is recommended: https://qdrant.github.io/qdrant/redoc/index.html#tag/collections/operation/recover_from_uploaded_snapshot
+
+If the file is too large, you can separately create a PersistentVolumeClaim, store your data in there, and refer to this separate PersistentVolumeClaim in this helm chart.
+
+Once you have created this PersistentVolumeClaim (must be in the same namespace as your Qdrant cluster), set the following values:
+
+```yml
+snapshotRestoration:
+ enabled: true
+ pvcName: ""
+ snapshots:
+ - /qdrant/snapshots///:
+```
+
+And run "helm upgrade". This will restart your cluster and restore the specified collection from the snapshot. Qdrant will refuse to overwrite an existing collection, so ensure the collection is deleted before restoring.
+
+After the snapshot is restored, remove the above values and run "helm upgrade" again to trigger another rolling restart. Otherwise, the snapshot restore will be attempted again if your cluster ever restarts.
+
+## Metrics endpoints
+
+Metrics are available through rest api (default port set to 6333) at `/metrics`
+
+Refer to [qdrant metrics configuration](https://qdrant.tech/documentation/telemetry/#metrics) for more information.
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/NOTES.txt b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/NOTES.txt
new file mode 100644
index 0000000..450736c
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/NOTES.txt
@@ -0,0 +1,24 @@
+Qdrant {{ .Chart.AppVersion }} has been deployed successfully.
+
+The full Qdrant documentation is available at https://qdrant.tech/documentation/.
+
+To forward Qdrant's ports execute one of the following commands:
+ export POD_NAME=$(kubectl get pods --namespace {{ $.Release.Namespace }} -l "app.kubernetes.io/name={{ include "qdrant.name" . }},app.kubernetes.io/instance={{ $.Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+
+{{- if contains "ClusterIP" .Values.service.type }}
+ {{- range .Values.service.ports }}
+
+If you want to use Qdrant via {{ .name }} execute the following commands
+ kubectl --namespace {{ $.Release.Namespace }} port-forward $POD_NAME {{ .targetPort }}:{{ .targetPort }}
+ {{- end }}
+{{- end }}
+
+{{- if .Values.ingress.enabled }}
+
+If you want to access Qdrant through the ingress controller
+{{- range $host := .Values.ingress.hosts }}
+ {{- range .paths }}
+ http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+ {{- end }}
+{{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/_helpers.tpl b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/_helpers.tpl
new file mode 100644
index 0000000..67bdd81
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/_helpers.tpl
@@ -0,0 +1,134 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "qdrant.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "qdrant.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "qdrant.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "qdrant.labels" -}}
+helm.sh/chart: {{ include "qdrant.chart" . }}
+{{ include "qdrant.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "qdrant.selectorLabels" -}}
+app: {{ include "qdrant.name" . }}
+app.kubernetes.io/name: {{ include "qdrant.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "qdrant.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "qdrant.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create secret
+*/}}
+{{- define "qdrant.secret" -}}
+{{- $readOnlyApiKey := false }}
+{{- $apiKey := false }}
+{{- if kindIs "map" .Values.apiKey -}}
+{{- if .Values.apiKey.valueFrom -}}
+{{- /* Retrieve the value from the secret as specified in valueFrom */ -}}
+{{- $secretName := .Values.apiKey.valueFrom.secretKeyRef.name -}}
+{{- $secretKey := .Values.apiKey.valueFrom.secretKeyRef.key -}}
+{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace $secretName) | default dict -}}
+{{- $secretData := (get $secretObj "data") | default dict -}}
+{{- $apiKey = (get $secretData $secretKey | b64dec) -}}
+{{- end -}}
+{{- else if .Values.apiKey | toJson | eq "true" -}}
+{{- /* Retrieve existing randomly generated api key or create a new one */ -}}
+{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (printf "%s-apikey" (include "qdrant.fullname" . ))) | default dict -}}
+{{- $secretData := (get $secretObj "data") | default dict -}}
+{{- $apiKey = (get $secretData "api-key" | b64dec) | default (randAlphaNum 32) -}}
+{{- else if .Values.apiKey -}}
+{{- $apiKey = .Values.apiKey -}}
+{{- end -}}
+{{- if kindIs "map" .Values.readOnlyApiKey -}}
+{{- if .Values.readOnlyApiKey.valueFrom -}}
+{{- /* Retrieve the value from the secret as specified in valueFrom */ -}}
+{{- $secretName := .Values.readOnlyApiKey.valueFrom.secretKeyRef.name -}}
+{{- $secretKey := .Values.readOnlyApiKey.valueFrom.secretKeyRef.key -}}
+{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace $secretName) | default dict -}}
+{{- $secretData := (get $secretObj "data") | default dict -}}
+{{- $readOnlyApiKey = (get $secretData $secretKey | b64dec) -}}
+{{- end -}}
+{{- else if eq (.Values.readOnlyApiKey | toJson) "true" -}}
+{{- /* retrieve existing randomly generated api key or create new one */ -}}
+{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (printf "%s-apikey" (include "qdrant.fullname" . ))) | default dict -}}
+{{- $secretData := (get $secretObj "data") | default dict -}}
+{{- $readOnlyApiKey = (get $secretData "read-only-api-key" | b64dec) | default (randAlphaNum 32) -}}
+{{- else if .Values.readOnlyApiKey -}}
+{{- $readOnlyApiKey = .Values.readOnlyApiKey -}}
+{{- end -}}
+{{- if and $apiKey $readOnlyApiKey -}}
+api-key: {{ $apiKey | b64enc }}
+read-only-api-key: {{ $readOnlyApiKey | b64enc }}
+local.yaml: {{ printf "service:\n api_key: %s\n read_only_api_key: %s" $apiKey $readOnlyApiKey | b64enc }}
+{{- else if $apiKey -}}
+api-key: {{ $apiKey | b64enc }}
+local.yaml: {{ printf "service:\n api_key: %s" $apiKey | b64enc }}
+{{- else if $readOnlyApiKey -}}
+read-only-api-key: {{ $readOnlyApiKey | b64enc }}
+local.yaml: {{ printf "service:\n read_only_api_key: %s" $readOnlyApiKey | b64enc }}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Protocol to use for inter cluster communication
+*/}}
+{{- define "qdrant.p2p.protocol" -}}
+{{ if eq (.Values.config.cluster.p2p.enable_tls | toJson) "true" -}}
+https
+{{- else -}}
+http
+{{- end -}}
+{{- end -}}
+
+{{/*
+Port to use for inter cluster communication
+*/}}
+{{- define "qdrant.p2p.port" -}}
+{{- default 6335 .Values.config.cluster.p2p.port -}}
+{{- end -}}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/configmap.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/configmap.yaml
new file mode 100644
index 0000000..e9ac267
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/configmap.yaml
@@ -0,0 +1,32 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ include "qdrant.fullname" . }}
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+{{- with .Values.additionalAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+data:
+ initialize.sh: |
+ #!/bin/sh
+ echo "Soft limits"
+ ulimit -a -S
+ echo "Hard limits"
+ ulimit -a -H
+ ulimit -n $(ulimit -Hn)
+ SET_INDEX=${HOSTNAME##*-}
+ {{- if and (.Values.snapshotRestoration.enabled) (eq (.Values.replicaCount | quote) (1 | quote)) }}
+ echo "Starting initializing for pod $SET_INDEX and snapshots restoration"
+ exec ./entrypoint.sh --uri '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-0.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}' {{ range .Values.snapshotRestoration.snapshots }} --snapshot {{ . }} {{ end }}
+ {{- else }}
+ echo "Starting initializing for pod $SET_INDEX"
+ if [ "$SET_INDEX" = "0" ]; then
+ exec ./entrypoint.sh --uri '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-0.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}'
+ else
+ exec ./entrypoint.sh --bootstrap '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-0.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}' --uri '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-'"$SET_INDEX"'.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}'
+ fi
+ {{ end }}
+ production.yaml: |
+ {{- tpl (toYaml .Values.config) . | nindent 4 }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/ingress.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/ingress.yaml
new file mode 100644
index 0000000..ffa4c86
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/ingress.yaml
@@ -0,0 +1,52 @@
+{{- if .Values.ingress.enabled -}}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "qdrant.fullname" . }}
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+ {{- with .Values.ingress.additionalLabels }}
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+{{- if or .Values.ingress.annotations .Values.additionalAnnotations }}
+ annotations:
+{{- with .Values.additionalAnnotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- with .Values.ingress.annotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- end }}
+spec:
+ {{- if .Values.ingress.ingressClassName }}
+ ingressClassName: {{ .Values.ingress.ingressClassName }}
+ {{- end }}
+ {{- with .Values.ingress.hosts }}
+ rules:
+ {{- range . }}
+ - host: {{ .host | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path | quote }}
+ pathType: {{ .pathType | default "Prefix" | quote }}
+ backend:
+ service:
+ name: {{ default .serviceName (include "qdrant.fullname" $) }}
+ port:
+ number: {{ .servicePort }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- range .Values.ingress.tls}}
+ - hosts:
+ {{- range .hosts }}
+ - {{ . | quote }}
+ {{- end }}
+ secretName: {{ .secretName | quote }}
+ {{- end }}
+ {{- end }}
+{{- end }}
+
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/pdb.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/pdb.yaml
new file mode 100644
index 0000000..9233542
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/pdb.yaml
@@ -0,0 +1,25 @@
+{{- if .Values.podDisruptionBudget.enabled }}
+apiVersion: policy/v1
+kind: PodDisruptionBudget
+metadata:
+ name: {{ include "qdrant.fullname" . }}
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+{{- with .Values.additionalAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+spec:
+ {{- with .Values.podDisruptionBudget.maxUnavailable}}
+ maxUnavailable: {{ . }}
+ {{- end }}
+ {{- with .Values.podDisruptionBudget.minAvailable }}
+ minAvailable: {{ . }}
+ {{- end }}
+ {{- with .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }}
+ unhealthyPodEvictionPolicy: {{ . }}
+ {{- end }}
+ selector:
+ matchLabels:
+ {{- include "qdrant.selectorLabels" . | nindent 6 }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/secret.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/secret.yaml
new file mode 100644
index 0000000..c9e30bf
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/secret.yaml
@@ -0,0 +1,14 @@
+{{- if or .Values.apiKey .Values.readOnlyApiKey }}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "qdrant.fullname" . }}-apikey
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+{{- with .Values.additionalAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+data:
+{{ include "qdrant.secret" . | indent 2}}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/service-headless.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/service-headless.yaml
new file mode 100644
index 0000000..b417b22
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/service-headless.yaml
@@ -0,0 +1,34 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "qdrant.fullname" . }}-headless
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+ app.kubernetes.io/component: cluster-discovery
+{{- with .Values.service.additionalLabels }}
+{{- toYaml . | nindent 4 }}
+{{- end }}
+{{- if or .Values.service.annotations .Values.additionalAnnotations }}
+ annotations:
+{{- with .Values.additionalAnnotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- with .Values.service.annotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- end }}
+spec:
+ clusterIP: None
+ publishNotReadyAddresses: true
+ ports:
+ {{- range .Values.service.ports }}
+ - name: {{ .name }}
+ port: {{ .port }}
+ targetPort: {{ .targetPort }}
+ protocol: {{ .protocol | default "TCP" }}
+ {{- if .appProtocol }}
+ appProtocol: {{ .appProtocol }}
+ {{- end }}
+ {{- end }}
+ selector:
+ {{- include "qdrant.selectorLabels" . | nindent 4 }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/service.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/service.yaml
new file mode 100644
index 0000000..797becf
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/service.yaml
@@ -0,0 +1,38 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "qdrant.fullname" . }}
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+{{- with .Values.service.additionalLabels }}
+{{- toYaml . | nindent 4 }}
+{{- end }}
+{{- if or .Values.service.annotations .Values.additionalAnnotations }}
+ annotations:
+{{- with .Values.additionalAnnotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- with .Values.service.annotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- end }}
+spec:
+ type: {{ .Values.service.type | default "ClusterIP" }}
+ {{- if .Values.service.loadBalancerIP }}
+ loadBalancerIP: {{ .Values.service.loadBalancerIP }}
+ {{- end }}
+ ports:
+ {{- range .Values.service.ports }}
+ - name: {{ .name }}
+ port: {{ .port }}
+ targetPort: {{ .targetPort }}
+ {{- if and (or (eq $.Values.service.type "NodePort") (eq $.Values.service.type "LoadBalancer")) (not (empty .nodePort)) }}
+ nodePort: {{ .nodePort }}
+ {{- end }}
+ protocol: {{ .protocol | default "TCP" }}
+ {{- if .appProtocol }}
+ appProtocol: {{ .appProtocol }}
+ {{- end }}
+ {{- end }}
+ selector:
+ {{- include "qdrant.selectorLabels" . | nindent 4 }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/serviceaccount.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/serviceaccount.yaml
new file mode 100644
index 0000000..721a5b5
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/serviceaccount.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "qdrant.fullname" . }}
+{{- if or .Values.serviceAccount.annotations .Values.additionalAnnotations }}
+ annotations:
+{{- with .Values.additionalAnnotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- with .Values.serviceAccount.annotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+{{- end }}
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/servicemonitor.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/servicemonitor.yaml
new file mode 100644
index 0000000..e1631e7
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/servicemonitor.yaml
@@ -0,0 +1,48 @@
+{{- if .Values.metrics.serviceMonitor.enabled }}
+kind: ServiceMonitor
+apiVersion: monitoring.coreos.com/v1
+metadata:
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+{{- with .Values.metrics.serviceMonitor.additionalLabels }}
+{{- toYaml . | nindent 4 }}
+{{- end }}
+ name: {{ include "qdrant.fullname" . }}
+{{- with .Values.additionalAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+spec:
+ endpoints:
+ - honorLabels: true
+ interval: {{ .Values.metrics.serviceMonitor.scrapeInterval }}
+ path: {{ .Values.metrics.serviceMonitor.targetPath }}
+ port: {{ .Values.metrics.serviceMonitor.targetPort }}
+ scheme: http
+ scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }}
+{{- if .Values.metrics.serviceMonitor.metricRelabelings }}
+ metricRelabelings:
+{{ tpl (toYaml .Values.metrics.serviceMonitor.metricRelabelings | indent 8) . }}
+{{- end }}
+{{- if .Values.metrics.serviceMonitor.relabelings }}
+ relabelings:
+{{ tpl (toYaml .Values.metrics.serviceMonitor.relabelings | indent 8) . }}
+{{- end }}
+{{- if .Values.readOnlyApiKey }}
+ authorization:
+ type: Bearer
+ credentials:
+ name: {{ include "qdrant.fullname" . }}-apikey
+ key: read-only-api-key
+{{- else if .Values.apiKey }}
+ authorization:
+ type: Bearer
+ credentials:
+ name: {{ include "qdrant.fullname" . }}-apikey
+ key: api-key
+{{- end }}
+ selector:
+ matchLabels:
+ {{- include "qdrant.labels" . | nindent 6 }}
+ app.kubernetes.io/component: cluster-discovery
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/statefulset.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/statefulset.yaml
new file mode 100644
index 0000000..c0aa419
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/statefulset.yaml
@@ -0,0 +1,294 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: {{ include "qdrant.fullname" . }}
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+{{- with .Values.additionalLabels }}
+{{- toYaml . | nindent 4 }}
+{{- end }}
+{{- with .Values.additionalAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+spec:
+ replicas: {{ .Values.replicaCount }}
+ podManagementPolicy: {{ .Values.podManagementPolicy }}
+ selector:
+ matchLabels:
+ {{- include "qdrant.selectorLabels" . | nindent 6 }}
+ serviceName: {{ include "qdrant.fullname" . }}-headless
+ template:
+ metadata:
+ annotations:
+ checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+ {{- if or .Values.apiKey .Values.readOnlyApiKey }}
+ checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
+ {{- end }}
+ {{- with .Values.podAnnotations }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "qdrant.selectorLabels" . | nindent 8 }}
+ {{- with .Values.podLabels }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- if .Values.priorityClassName }}
+ priorityClassName: "{{ .Values.priorityClassName }}"
+ {{- end }}
+ {{- if .Values.shareProcessNamespace }}
+ shareProcessNamespace: {{ .Values.shareProcessNamespace }}
+ {{- end }}
+ initContainers:
+ {{- if and .Values.updateVolumeFsOwnership (not .Values.image.useUnprivilegedImage) }}
+ {{- if and .Values.containerSecurityContext .Values.containerSecurityContext.runAsUser }}
+ - name: ensure-dir-ownership
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ command:
+ - chown
+ - -R
+ - {{ int64 .Values.containerSecurityContext.runAsUser }}:{{ int64 .Values.podSecurityContext.fsGroup }}
+ - /qdrant/storage
+ - /qdrant/snapshots
+ {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }}
+ - {{ .Values.snapshotRestoration.mountPath }}
+ {{- end }}
+ volumeMounts:
+ - name: {{ .Values.persistence.storageVolumeName | default "qdrant-storage" }}
+ mountPath: /qdrant/storage
+ {{- if .Values.persistence.storageSubPath }}
+ subPath: "{{ .Values.persistence.storageSubPath }}"
+ {{- end }}
+ - name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }}
+ mountPath: /qdrant/snapshots
+ {{- if .Values.snapshotPersistence.snapshotsSubPath }}
+ subPath: "{{ .Values.snapshotPersistence.snapshotsSubPath }}"
+ {{- end }}
+ {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }}
+ - name: qdrant-snapshot-restoration
+ mountPath: {{ .Values.snapshotRestoration.mountPath }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
+ containers:
+ {{- if .Values.sidecarContainers -}}
+ {{- toYaml .Values.sidecarContainers | trim | nindent 8 }}
+ {{- end}}
+ - name: {{ .Chart.Name }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}{{ .Values.image.useUnprivilegedImage | ternary "-unprivileged" "" }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ env:
+ - name: QDRANT_INIT_FILE_PATH
+ value: /qdrant/init/.qdrant-initialized
+ {{- range .Values.env }}
+ - name: {{ .name }}
+ {{- if .valueFrom }}
+ valueFrom: {{- toYaml .valueFrom | nindent 16 }}
+ {{- else }}
+ value: {{ .value | quote }}
+ {{- end }}
+ {{- end }}
+ command: ["/bin/bash", "-c"]
+ {{- with .Values.args }}
+ args:
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ ports:
+ {{- range .Values.service.ports }}
+ - name: {{ .name }}
+ containerPort: {{ .targetPort }}
+ protocol: {{ .protocol }}
+ {{- end }}
+
+ {{- $values := .Values -}}
+ {{- range .Values.service.ports }}
+ {{- if and $values.livenessProbe.enabled .checksEnabled }}
+ livenessProbe:
+ {{- if eq .name "grpc"}}
+ grpc:
+ port: {{ .targetPort }}
+ {{- end }}
+ {{- if eq .name "http"}}
+ httpGet:
+ path: /
+ port: {{ .targetPort }}
+ {{- if and $values.config.service $values.config.service.enable_tls }}
+ scheme: HTTPS
+ {{- end }}
+ {{- end }}
+ initialDelaySeconds: {{ $values.livenessProbe.initialDelaySeconds }}
+ timeoutSeconds: {{ $values.livenessProbe.timeoutSeconds }}
+ periodSeconds: {{ $values.livenessProbe.periodSeconds }}
+ successThreshold: {{ $values.livenessProbe.successThreshold }}
+ failureThreshold: {{ $values.livenessProbe.failureThreshold }}
+ {{- end }}
+ {{- if and $values.readinessProbe.enabled .checksEnabled }}
+ readinessProbe:
+ {{- if eq .name "grpc"}}
+ grpc:
+ port: {{ .targetPort }}
+ {{- end }}
+ {{- if eq .name "http"}}
+ httpGet:
+ path: "{{- if semverCompare ">=1.7.3" ($.Values.image.tag | default $.Chart.AppVersion) -}}/readyz{{else}}/{{end}}"
+ port: {{ .targetPort }}
+ {{- if and $values.config.service $values.config.service.enable_tls }}
+ scheme: HTTPS
+ {{- end }}
+ {{- end }}
+ initialDelaySeconds: {{ $values.readinessProbe.initialDelaySeconds }}
+ timeoutSeconds: {{ $values.readinessProbe.timeoutSeconds }}
+ periodSeconds: {{ $values.readinessProbe.periodSeconds }}
+ successThreshold: {{ $values.readinessProbe.successThreshold }}
+ failureThreshold: {{ $values.readinessProbe.failureThreshold }}
+ {{- end }}
+ {{- if and $values.startupProbe.enabled .checksEnabled }}
+ startupProbe:
+ {{- if eq .name "grpc"}}
+ grpc:
+ port: {{ .targetPort }}
+ {{- end }}
+ {{- if eq .name "http"}}
+ httpGet:
+ path: /
+ port: {{ .targetPort }}
+ {{- if and $values.config.service $values.config.service.enable_tls }}
+ scheme: HTTPS
+ {{- end }}
+ {{- end }}
+ initialDelaySeconds: {{ $values.startupProbe.initialDelaySeconds }}
+ timeoutSeconds: {{ $values.startupProbe.timeoutSeconds }}
+ periodSeconds: {{ $values.startupProbe.periodSeconds }}
+ successThreshold: {{ $values.startupProbe.successThreshold }}
+ failureThreshold: {{ $values.startupProbe.failureThreshold }}
+ {{- end }}
+ {{- end }}
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+ {{- with .Values.containerSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ {{- with .Values.lifecycle }}
+ lifecycle:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ volumeMounts:
+ - name: {{ .Values.persistence.storageVolumeName | default "qdrant-storage" }}
+ mountPath: /qdrant/storage
+ {{- if .Values.persistence.storageSubPath }}
+ subPath: "{{ .Values.persistence.storageSubPath }}"
+ {{- end }}
+ - name: qdrant-config
+ mountPath: /qdrant/config/initialize.sh
+ subPath: initialize.sh
+ - name: qdrant-config
+ mountPath: /qdrant/config/production.yaml
+ subPath: production.yaml
+ {{- if or .Values.apiKey .Values.readOnlyApiKey }}
+ - name: qdrant-secret
+ mountPath: /qdrant/config/local.yaml
+ subPath: local.yaml
+ {{- end }}
+ {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }}
+ - name: qdrant-snapshot-restoration
+ mountPath: {{ .Values.snapshotRestoration.mountPath }}
+ {{- end }}
+ - name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }}
+ mountPath: /qdrant/snapshots
+ {{- if .Values.snapshotPersistence.snapshotsSubPath }}
+ subPath: "{{ .Values.snapshotPersistence.snapshotsSubPath }}"
+ {{- end }}
+ - name: qdrant-init
+ mountPath: /qdrant/init
+ {{- if .Values.additionalVolumeMounts }}
+{{- toYaml .Values.additionalVolumeMounts | default "" | nindent 10 }}
+ {{- end}}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- tpl (toYaml .) $ | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.topologySpreadConstraints}}
+ topologySpreadConstraints:
+ {{- tpl (toYaml .) $ | nindent 8 }}
+ {{- end }}
+ serviceAccountName: {{ include "qdrant.fullname" . }}
+ volumes:
+ - name: qdrant-config
+ configMap:
+ name: {{ include "qdrant.fullname" . }}
+ defaultMode: 0755
+ {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }}
+ - name: qdrant-snapshot-restoration
+ persistentVolumeClaim:
+ claimName: {{ .Values.snapshotRestoration.pvcName }}
+ {{- end }}
+ {{- if not .Values.snapshotPersistence.enabled }}
+ - name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }}
+ emptyDir: {}
+ {{- end }}
+ - name: qdrant-init
+ emptyDir: {}
+ {{- if or .Values.apiKey .Values.readOnlyApiKey }}
+ - name: qdrant-secret
+ secret:
+ secretName: {{ include "qdrant.fullname" . }}-apikey
+ defaultMode: 0600
+ {{- end }}
+ {{- if .Values.additionalVolumes }}
+{{- toYaml .Values.additionalVolumes | default "" | nindent 8 }}
+ {{- end}}
+ volumeClaimTemplates:
+ - metadata:
+ name: {{ .Values.persistence.storageVolumeName | default "qdrant-storage" }}
+ labels:
+ app: {{ template "qdrant.name" . }}
+ {{- with .Values.persistence.annotations }}
+ annotations:
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ spec:
+ storageClassName: {{ .Values.persistence.storageClassName }}
+ accessModes:
+ {{- range .Values.persistence.accessModes }}
+ - {{ . | quote }}
+ {{- end }}
+ resources:
+ requests:
+ storage: {{ .Values.persistence.size | quote }}
+ {{- if .Values.snapshotPersistence.enabled }}
+ - metadata:
+ name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }}
+ labels:
+ app: {{ template "qdrant.name" . }}
+ {{- with .Values.snapshotPersistence.annotations }}
+ annotations:
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ spec:
+ storageClassName: {{ .Values.snapshotPersistence.storageClassName }}
+ accessModes:
+ {{- range .Values.snapshotPersistence.accessModes }}
+ - {{ . | quote }}
+ {{- end }}
+ resources:
+ requests:
+ storage: {{ .Values.snapshotPersistence.size | quote }}
+ {{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/tests/test-db-interaction.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/tests/test-db-interaction.yaml
new file mode 100644
index 0000000..c6837b2
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/templates/tests/test-db-interaction.yaml
@@ -0,0 +1,126 @@
+{{- $root := . }}
+{{- $namespace := .Release.Namespace }}
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "{{ include "qdrant.fullname" . }}-test-db-interaction"
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+{{- with .Values.additionalAnnotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+spec:
+ containers:
+ - name: test-script
+ image: {{ .Values.chartTests.dbInteraction.image | quote }}
+ args: ['bash', '/app/entrypoint.sh']
+ volumeMounts:
+ - mountPath: /app
+ name: test-script
+ {{- if .Values.additionalVolumeMounts }}
+{{- toYaml .Values.additionalVolumeMounts | default "" | nindent 8 }}
+ {{- end}}
+ resources:
+ {{- toYaml .Values.chartTests.dbInteraction.resources | nindent 8 }}
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ volumes:
+ - name: test-script
+ configMap:
+ name: "{{ include "qdrant.fullname" . }}-test-db-interaction"
+ {{- if .Values.additionalVolumes }}
+{{- toYaml .Values.additionalVolumes | default "" | nindent 4 }}
+ {{- end}}
+ restartPolicy: Never
+ serviceAccountName: {{ include "qdrant.fullname" . }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: "{{ include "qdrant.fullname" . }}-test-db-interaction"
+ labels:
+ {{- include "qdrant.labels" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+{{- with .Values.additionalAnnotations }}
+ {{- toYaml . | nindent 4 }}
+{{- end }}
+data:
+ entrypoint.sh: |
+ #!/bin/bash
+ set -xe
+ # Kind's networking is very flaky
+ echo 'connect-timeout = 5' > $HOME/.curlrc
+ echo 'retry = 60' >> $HOME/.curlrc
+ echo 'retry-delay = 5' >> $HOME/.curlrc
+ echo 'retry-all-errors' >> $HOME/.curlrc
+ # Don't clutter the logs with progress bars
+ echo 'no-progress-meter' >> $HOME/.curlrc
+ # Ensure errors cause the script to fail, but show the response body
+ echo 'fail-with-body' >> $HOME/.curlrc
+
+ if [ -d /mnt/secrets/certs ]; then
+ cp /mnt/secrets/certs/ca.pem /usr/share/pki/trust/anchors/private-ca.pem
+ update-ca-certificates
+ fi
+
+ QDRANT_COLLECTION="test_collection"
+ {{- range .Values.service.ports }}
+ {{- if eq .name "http" }}
+ echo "Connecting to {{ include "qdrant.fullname" $root }}.{{ $namespace }}:{{ .port }}"
+ QDRANT_URL="http://{{ include "qdrant.fullname" $root }}.{{ $namespace }}:{{ .port }}"
+ {{- if and $root.Values.config.service $root.Values.config.service.enable_tls }}
+ echo "Using https"
+ QDRANT_URL="https://{{ include "qdrant.fullname" $root }}.{{ $namespace }}:{{ .port }}"
+ {{- end }}
+ {{- end }}
+ {{- end }}
+ API_KEY_HEADER=""
+ {{- if .Values.apiKey }}
+ API_KEY_HEADER="Api-key: {{ .Values.apiKey }}"
+ {{- else if .Values.readOnlyApiKey }}
+ API_KEY_HEADER="Api-key: {{ .Values.readOnlyApiKey }}"
+ {{- end }}
+
+ # Delete collection if exists
+ curl -X DELETE -H "${API_KEY_HEADER}" $QDRANT_URL/collections/${QDRANT_COLLECTION}
+
+ # Create collection
+ curl -X PUT \
+ -H 'Content-Type: application-json' \
+ -d '{"vectors":{"size":4,"distance":"Dot"}}' \
+ -H "${API_KEY_HEADER}" \
+ $QDRANT_URL/collections/${QDRANT_COLLECTION}
+
+ # Insert points
+ curl -X PUT \
+ -H 'Content-Type: application-json' \
+ -d '{"points":[
+ {"id":1,"vector":[0.05, 0.61, 0.76, 0.74],"payload":{"city":"Berlin"}},
+ {"id":2,"vector":[0.19, 0.81, 0.75, 0.11],"payload":{"city":"London"}},
+ {"id":3,"vector":[0.36, 0.55, 0.47, 0.94],"payload":{"city":"Moscow"}},
+ {"id":4,"vector":[0.18, 0.01, 0.85, 0.80],"payload":{"city":"New York"}},
+ {"id":5,"vector":[0.24, 0.18, 0.22, 0.44],"payload":{"city":"Beijing"}},
+ {"id":6,"vector":[0.35, 0.08, 0.11, 0.44],"payload":{"city":"Mumbai"}}
+ ]}' \
+ -H "${API_KEY_HEADER}" \
+ $QDRANT_URL/collections/${QDRANT_COLLECTION}/points
+
+ # Run query
+ curl -X POST \
+ -H 'Content-Type: application-json' \
+ -d '{"vector":[0.2, 0.1, 0.9, 0.7],"limit":3}' \
+ -H "${API_KEY_HEADER}" \
+ $QDRANT_URL/collections/${QDRANT_COLLECTION}/points/search
diff --git a/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/values.yaml b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/values.yaml
new file mode 100644
index 0000000..77bc0b8
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/charts/qdrant/values.yaml
@@ -0,0 +1,277 @@
+replicaCount: 1
+
+image:
+ repository: docker.io/qdrant/qdrant
+ pullPolicy: IfNotPresent
+ tag: ""
+ useUnprivilegedImage: false
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+args: ["./config/initialize.sh"]
+env: {}
+ # - name: QDRANT_ALLOW_RECOVERY_MODE
+ # value: true
+
+# checks - Readiness and liveness checks can only be enabled for either http (REST) or grpc (multiple checks not supported)
+# grpc checks are only available from k8s 1.24+ so as of per default we check http
+service:
+ type: ClusterIP
+ additionalLabels: {}
+ annotations: {}
+ loadBalancerIP: ""
+ ports:
+ - name: http
+ port: 6333
+ targetPort: 6333
+ protocol: TCP
+ checksEnabled: true
+ # appProtocol: http
+ - name: grpc
+ port: 6334
+ targetPort: 6334
+ protocol: TCP
+ checksEnabled: false
+ # appProtocol: http2
+ - name: p2p
+ port: 6335
+ targetPort: 6335
+ protocol: TCP
+ checksEnabled: false
+
+ingress:
+ enabled: false
+ ingressClassName: ""
+ additionalLabels: {}
+ annotations: {}
+ # kubernetes.io/ingress.class: alb
+ hosts:
+ - host: example-domain.com
+ paths:
+ - path: /
+ pathType: Prefix
+ servicePort: 6333
+ tls: []
+ # - hosts:
+ # - example-domain.com
+ # secretName: tls-secret-name
+
+livenessProbe:
+ enabled: false
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ timeoutSeconds: 1
+ failureThreshold: 6
+ successThreshold: 1
+
+readinessProbe:
+ enabled: true
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ timeoutSeconds: 1
+ failureThreshold: 6
+ successThreshold: 1
+
+startupProbe:
+ enabled: false
+ initialDelaySeconds: 10
+ periodSeconds: 5
+ timeoutSeconds: 1
+ failureThreshold: 30
+ successThreshold: 1
+
+additionalLabels: {}
+# additionalAnnotations will be added to all top-level resources (StatefulSet, Service, ConfigMap, etc.)
+additionalAnnotations: {}
+podAnnotations: {}
+podLabels: {}
+
+resources: {}
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+containerSecurityContext:
+ runAsNonRoot: true
+ runAsUser: 1000
+ runAsGroup: 2000
+ allowPrivilegeEscalation: false
+ privileged: false
+ readOnlyRootFilesystem: true
+
+podSecurityContext:
+ fsGroup: 3000
+ fsGroupChangePolicy: Always
+
+lifecycle:
+ preStop:
+ exec:
+ # Sleeping before shutdown allows Qdrant to process requests that were
+ # in-flight before the node is removed from load-balancing.
+ # If using an external load balancer, you may need to increase this
+ # duration to be greater than the LB's health check interval.
+ command: ["sleep", "3"]
+
+# Unless .Values.image.useUnprivilegedImage is set to true, ensures that the pre-existing
+# files on the storage and snapshot volume are owned by the container's user and fsGroup.
+updateVolumeFsOwnership: true
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+ # podAntiAffinity:
+ # requiredDuringSchedulingIgnoredDuringExecution:
+ # - labelSelector:
+ # matchExpressions:
+ # - key: app.kubernetes.io/name
+ # operator: In
+ # values:
+ # - '{{ include "qdrant.name" . }}'
+ # - key: app.kubernetes.io/instance
+ # operator: In
+ # values:
+ # - '{{ .Release.Name }}'
+ # topologyKey: "kubernetes.io/hostname"
+
+topologySpreadConstraints: []
+
+persistence:
+ accessModes: ["ReadWriteOnce"]
+ size: 10Gi
+ annotations: {}
+ # storageVolumeName: qdrant-storage
+ # storageSubPath: ""
+ # storageClassName: local-path
+
+# If you use snapshots or the snapshot shard transfer mechanism, we recommend
+# creating a separate volume of the same size as your main volume so that your
+# cluster won't crash if the snapshot is too big.
+snapshotPersistence:
+ enabled: false
+ accessModes: ["ReadWriteOnce"]
+ size: 10Gi
+ annotations: {}
+ # snapshotsVolumeName: qdrant-snapshots
+ # snapshotsSubPath: ""
+ # You can change the storageClassName to ensure snapshots are saved to cold storage.
+ # storageClassName: local-path
+
+snapshotRestoration:
+ enabled: false
+ # Set pvcName if you want to restore from a separately-created PVC. Only supported for single-node clusters unless the PVC is ReadWriteMany.
+ # If you set snapshotPersistence.enabled and want to restore a snapshot from there, you can leave this blank to skip mounting an external volume.
+ pvcName: snapshots-pvc
+ # Must not conflict with /qdrant/snapshots or /qdrant/storage
+ mountPath: /qdrant/snapshot-restoration
+ snapshots:
+ # - /qdrant/snapshot-restoration/test_collection/test_collection-2022-10-24-13-56-50.snapshot:test_collection
+
+# modification example for configuration to overwrite defaults
+config:
+ cluster:
+ enabled: true
+ p2p:
+ port: 6335
+ enable_tls: false
+ consensus:
+ tick_period_ms: 100
+
+sidecarContainers: []
+# sidecarContainers:
+# - name: my-sidecar
+# image: qdrant/my-sidecar-image
+# imagePullPolicy: Always
+# ports:
+# - name: my-port
+# containerPort: 5000
+# protocol: TCP
+# resources:
+# requests:
+# memory: 10Mi
+# cpu: 10m
+# limits:
+# memory: 100Mi
+# cpu: 100m
+
+metrics:
+ serviceMonitor:
+ enabled: false
+ additionalLabels: {}
+ scrapeInterval: 30s
+ scrapeTimeout: 10s
+ targetPort: http
+ targetPath: "/metrics"
+ ## MetricRelabelConfigs to apply to samples after scraping, but before ingestion.
+ ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#relabelconfig
+ ##
+ metricRelabelings: []
+ ## RelabelConfigs to apply to samples before scraping
+ ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#relabelconfig
+ ##
+ relabelings: []
+
+serviceAccount:
+ annotations: {}
+
+priorityClassName: ""
+
+shareProcessNamespace: false
+
+# We discourage changing this setting. Using the "OrderedReady" policy in a
+# multi-node cluster will cause a deadlock where nodes refuse to become
+# "Ready" until all nodes are running.
+podManagementPolicy: Parallel
+
+podDisruptionBudget:
+ enabled: false
+ maxUnavailable: 1
+ # do not enable if you are using not in 1.27
+ unhealthyPodEvictionPolicy: ""
+ # minAvailable: 1
+
+# api key for authentication at qdrant
+# false: no api key will be configured
+# true: an api key will be auto-generated
+# string: the given string will be set as an apikey
+# Also supports reading in from an external secret using
+# valueFrom:
+# secretKeyRef:
+# name:
+# key:
+# apiKey: false
+
+# read-only api key for authentication at qdrant
+# false: no read-only api key will be configured
+# true: an read-only api key will be auto-generated
+# string: the given string will be set as a read-only apikey
+# Also supports reading in from an external secret using
+# valueFrom:
+# secretKeyRef:
+# name:
+# key:
+# readOnlyApiKey: false
+
+additionalVolumes: []
+# - name: volumeName
+# emptyDir: {}
+
+additionalVolumeMounts: []
+# - name: volumeName
+# mountPath: "/mount/path"
+
+chartTests:
+ dbInteraction:
+ image: registry.suse.com/bci/bci-base:latest
+ resources:
+ requests:
+ cpu: 100m
+ memory: 200Mi
+ limits:
+ cpu: 100m
+ memory: 200Mi
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/NOTES.txt b/charts/dot-ai-stack/charts/dot-ai/templates/NOTES.txt
new file mode 100644
index 0000000..82b6e77
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/NOTES.txt
@@ -0,0 +1,27 @@
+=== dot-ai installed ===
+
+{{ if .Values.dex.enabled -}}
+OAuth authentication is enabled via Dex OIDC provider.
+
+Credentials secret: {{ .Values.dex.existingSecret }}
+Built-in admin: admin@dot-ai.local (password is whatever you hashed in dex.adminPasswordHash)
+
+IMPORTANT: Ensure dex.envFrom includes your existingSecret so Dex can read DEX_CLIENT_SECRET:
+ dex:
+ envFrom:
+ - secretRef:
+ name: {{ .Values.dex.existingSecret }}
+
+MCP clients authenticate via OAuth2 Authorization Code flow with PKCE.
+Authorization server metadata:
+ {{ include "dot-ai.externalUrl" . }}/.well-known/oauth-authorization-server
+
+{{- if .Values.dex.connectors }}
+
+Configured IdP connectors: {{ len .Values.dex.connectors }}
+{{- end }}
+{{ else -}}
+Authentication: Legacy bearer token mode.
+ Set secrets.auth.token in values.yaml or create the Secret externally.
+ Enable dex.enabled=true for OAuth/OIDC authentication.
+{{ end -}}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/_helpers.tpl b/charts/dot-ai-stack/charts/dot-ai/templates/_helpers.tpl
new file mode 100644
index 0000000..c4e0b59
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/_helpers.tpl
@@ -0,0 +1,194 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "dot-ai.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "dot-ai.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "dot-ai.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "dot-ai.labels" -}}
+helm.sh/chart: {{ include "dot-ai.chart" . }}
+{{ include "dot-ai.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "dot-ai.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "dot-ai.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "dot-ai.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "dot-ai.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
+
+{{/*
+Merge global annotations with resource-specific annotations.
+Resource-specific annotations take precedence over global annotations.
+Usage: include "dot-ai.annotations" (dict "global" .Values.annotations "local" .Values.ingress.annotations)
+*/}}
+{{- define "dot-ai.annotations" -}}
+{{- $global := .global | default dict -}}
+{{- $local := .local | default dict -}}
+{{- $merged := merge (deepCopy $local) $global -}}
+{{- if $merged -}}
+{{- toYaml $merged -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Build DOT_AI_PLUGINS_CONFIG JSON from enabled plugins.
+Supports two modes:
+ - Deployed: image + port β auto-generates endpoint URL
+ - External: endpoint β uses provided URL
+*/}}
+{{/*
+Dex external host β derived from the main ingress/gateway host.
+Prepends "dex." to the main host (e.g., dot-ai.example.com β dex.dot-ai.example.com).
+*/}}
+{{- define "dot-ai.dexExternalHost" -}}
+{{- $host := "" -}}
+{{- if .Values.ingress.enabled -}}
+ {{- $host = .Values.ingress.host -}}
+{{- else if .Values.gateway.listeners.https.hostname -}}
+ {{- $host = .Values.gateway.listeners.https.hostname -}}
+{{- else if .Values.gateway.listeners.http.hostname -}}
+ {{- $host = .Values.gateway.listeners.http.hostname -}}
+{{- end -}}
+{{- if $host -}}
+dex.{{ $host }}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Dex external URL β full URL including scheme and optional port.
+Used as the Dex issuer URL and for browser redirects.
+*/}}
+{{- define "dot-ai.dexExternalUrl" -}}
+{{- if .Values.dex.externalUrl -}}
+{{ .Values.dex.externalUrl }}
+{{- else -}}
+{{- $host := include "dot-ai.dexExternalHost" . -}}
+{{- if $host -}}
+{{- if or .Values.ingress.tls.enabled .Values.gateway.listeners.https.hostname -}}
+https://{{ $host }}
+{{- else -}}
+http://{{ $host }}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+dot-ai external URL β full URL of the main MCP server.
+Used for OAuth callback redirect URI.
+*/}}
+{{- define "dot-ai.externalUrl" -}}
+{{- if .Values.externalUrl -}}
+{{ .Values.externalUrl }}
+{{- else if .Values.ingress.enabled -}}
+ {{- if .Values.ingress.tls.enabled -}}
+https://{{ .Values.ingress.host }}
+ {{- else -}}
+http://{{ .Values.ingress.host }}
+ {{- end -}}
+{{- else if .Values.gateway.listeners.https.hostname -}}
+https://{{ .Values.gateway.listeners.https.hostname }}
+{{- else if .Values.gateway.listeners.http.hostname -}}
+http://{{ .Values.gateway.listeners.http.hostname }}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Dex in-cluster token endpoint β for server-to-server token exchange.
+The MCP server pod uses this URL (not the external one) to talk to Dex.
+*/}}
+{{- define "dot-ai.dexTokenEndpoint" -}}
+http://{{ .Release.Name }}-dex.{{ .Release.Namespace }}.svc.cluster.local:5556/token
+{{- end -}}
+
+{{/*
+Dex in-cluster gRPC endpoint β for user management via Dex gRPC API (PRD #380 Task 2.5).
+*/}}
+{{- define "dot-ai.dexGrpcEndpoint" -}}
+{{ .Release.Name }}-dex.{{ .Release.Namespace }}.svc.cluster.local:5557
+{{- end -}}
+
+{{/*
+MCP Servers Configuration (PRD #358)
+Generates JSON array of MCP server configs for discovery by dot-ai.
+Each entry includes: name, endpoint, attachTo.
+*/}}
+{{- define "dot-ai.mcpServersConfig" -}}
+{{- $servers := list -}}
+{{- range $name, $config := .Values.mcpServers -}}
+{{- if $config.enabled -}}
+{{- if not $config.endpoint -}}
+{{- fail (printf "mcpServers.%s is enabled but has no endpoint configured" $name) -}}
+{{- end -}}
+{{- $server := dict "name" $name "endpoint" $config.endpoint "attachTo" $config.attachTo -}}
+{{- $servers = append $servers $server -}}
+{{- end -}}
+{{- end -}}
+{{- $servers | toJson -}}
+{{- end -}}
+
+{{- define "dot-ai.pluginsConfig" -}}
+{{- $plugins := list -}}
+{{- range $name, $config := .Values.plugins -}}
+{{- if $config.enabled -}}
+{{- $endpoint := "" -}}
+{{- if $config.endpoint -}}
+{{- $endpoint = $config.endpoint -}}
+{{- else if $config.image -}}
+{{- $port := required (printf "plugins.%s.port is required when image is set" $name) $config.port -}}
+{{- $endpoint = printf "http://%s-%s:%d" $.Release.Name $name (int $port) -}}
+{{- else -}}
+{{- fail (printf "plugins.%s is enabled but has neither endpoint nor image configured" $name) -}}
+{{- end -}}
+{{- if $endpoint -}}
+{{- $plugins = append $plugins (dict "name" $name "url" $endpoint) -}}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+{{- $plugins | toJson -}}
+{{- end -}}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/clusterrole.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/clusterrole.yaml
new file mode 100644
index 0000000..db66145
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/clusterrole.yaml
@@ -0,0 +1,25 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ include "dot-ai.fullname" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+rules:
+# Full read access to discover any resource type
+- apiGroups: ["*"]
+ resources: ["*"]
+ verbs: ["get", "list", "watch"]
+# Full write access to deploy any resource type
+- apiGroups: ["*"]
+ resources: ["*"]
+ verbs: ["create", "update", "patch", "delete"]
+# Non-resource URLs (needed for discovery)
+- nonResourceURLs: ["*"]
+ verbs: ["get"]
+{{- end }}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/clusterrolebinding.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/clusterrolebinding.yaml
new file mode 100644
index 0000000..bb1bb6c
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/clusterrolebinding.yaml
@@ -0,0 +1,21 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{ include "dot-ai.fullname" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ include "dot-ai.fullname" . }}
+subjects:
+- kind: ServiceAccount
+ name: {{ include "dot-ai.serviceAccountName" . }}
+ namespace: {{ .Release.Namespace }}
+{{- end }}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/deployment.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/deployment.yaml
new file mode 100644
index 0000000..a7552fe
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/deployment.yaml
@@ -0,0 +1,244 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "dot-ai.fullname" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ {{- include "dot-ai.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ labels:
+ {{- include "dot-ai.selectorLabels" . | nindent 8 }}
+ {{- $podAnnotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $podAnnotations }}
+ annotations:
+ {{- $podAnnotations | nindent 8 }}
+ {{- end }}
+ spec:
+ serviceAccountName: {{ include "dot-ai.serviceAccountName" . }}
+ containers:
+ - name: mcp-server
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}
+ ports:
+ - containerPort: 3456
+ name: http
+ protocol: TCP
+ env:
+ - name: TRANSPORT_TYPE
+ value: "http"
+ - name: PORT
+ value: "3456"
+ - name: HOST
+ value: "0.0.0.0"
+ - name: SESSION_MODE
+ value: "stateful"
+ # AI Provider configuration
+ - name: AI_PROVIDER
+ value: {{ .Values.ai.provider | default "anthropic" | quote }}
+{{- if .Values.ai.model }}
+ - name: AI_MODEL
+ value: {{ .Values.ai.model | quote }}
+{{- end }}
+{{- if .Values.ai.customEndpoint.enabled }}
+ - name: CUSTOM_LLM_BASE_URL
+ value: {{ .Values.ai.customEndpoint.baseURL | quote }}
+ - name: CUSTOM_EMBEDDINGS_BASE_URL
+ value: {{ .Values.ai.customEndpoint.embeddingsBaseURL | default .Values.ai.customEndpoint.baseURL | quote }}
+{{- if .Values.ai.customEndpoint.embeddingsModel }}
+ - name: EMBEDDINGS_MODEL
+ value: {{ .Values.ai.customEndpoint.embeddingsModel | quote }}
+{{- end }}
+{{- if .Values.ai.customEndpoint.embeddingsDimensions }}
+ - name: EMBEDDINGS_DIMENSIONS
+ value: {{ .Values.ai.customEndpoint.embeddingsDimensions | quote }}
+{{- end }}
+{{- end }}
+{{- if .Values.localEmbeddings.enabled }}
+ # Local embedding service auto-fallback (PRD #384)
+ # Placed before secretKeyRef entries so real keys from external secrets override the dummy
+ - name: CUSTOM_EMBEDDINGS_API_KEY
+ value: "local-embeddings-no-key-required"
+ - name: CUSTOM_EMBEDDINGS_BASE_URL
+ value: "http://{{ .Release.Name }}-local-embeddings.{{ .Release.Namespace }}.svc.cluster.local:80/v1"
+ - name: EMBEDDINGS_DIMENSIONS
+ value: {{ .Values.localEmbeddings.dimensions | quote }}
+{{- end }}
+ # AI Provider API Keys
+ - name: ANTHROPIC_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.anthropic.keyName }}
+ optional: true
+ - name: OPENAI_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.openai.keyName }}
+ optional: true
+ - name: GOOGLE_GENERATIVE_AI_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.google.keyName }}
+ optional: true
+ - name: XAI_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.xai.keyName }}
+ optional: true
+ - name: MOONSHOT_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.moonshot.keyName }}
+ optional: true
+ - name: ALIBABA_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.alibaba.keyName }}
+ optional: true
+ - name: CUSTOM_LLM_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.customLlm.keyName | default "custom-llm-api-key" }}
+ optional: true
+ - name: CUSTOM_EMBEDDINGS_API_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.customEmbeddings.keyName | default "custom-embeddings-api-key" }}
+ optional: true
+ # PRD #359: QDRANT_URL moved to agentic-tools plugin (all vector ops go through plugin)
+ # Bearer token authentication (required)
+ - name: DOT_AI_AUTH_TOKEN
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: {{ .Values.secrets.auth.keyName }}
+{{- if .Values.rbac.enforcement.enabled }}
+ # RBAC enforcement (PRD #392)
+ - name: DOT_AI_RBAC_ENABLED
+ value: "true"
+{{- end }}
+{{- if .Values.dex.enabled }}
+ # Dex OIDC configuration (PRD #380)
+ # DEX_CLIENT_SECRET and DOT_AI_JWT_SECRET injected via envFrom below
+ - name: DEX_ISSUER_URL
+ value: {{ include "dot-ai.dexExternalUrl" . | quote }}
+ - name: DEX_TOKEN_ENDPOINT
+ value: {{ include "dot-ai.dexTokenEndpoint" . | quote }}
+ - name: DEX_CLIENT_ID
+ value: "dot-ai"
+ - name: DOT_AI_EXTERNAL_URL
+ value: {{ include "dot-ai.externalUrl" . | quote }}
+ - name: DEX_GRPC_ENDPOINT
+ value: {{ include "dot-ai.dexGrpcEndpoint" . | quote }}
+ - name: POD_NAMESPACE
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.namespace
+{{- end }}
+ # Git authentication (PRD #362: Git Operations)
+ # Note: DOT_AI_GIT_TOKEN is configured via extraEnv - reused from user-prompts feature
+ - name: GITHUB_APP_ENABLED
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: github-app-enabled
+ optional: true
+ - name: GITHUB_APP_ID
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: github-app-id
+ optional: true
+ - name: GITHUB_APP_PRIVATE_KEY
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: github-app-private-key
+ optional: true
+ - name: GITHUB_APP_INSTALLATION_ID
+ valueFrom:
+ secretKeyRef:
+ name: {{ .Values.secrets.name }}
+ key: github-app-installation-id
+ optional: true
+ # Web UI integration (PRD #317: Query Visualization)
+{{- if .Values.webUI.baseUrl }}
+ - name: WEB_UI_BASE_URL
+ value: {{ .Values.webUI.baseUrl | quote }}
+{{- end }}
+ # Note: KUBERNETES_SERVICE_HOST is automatically set by k8s and triggers in-cluster config
+{{- with .Values.extraEnv }}
+ # Additional environment variables
+ {{- toYaml . | nindent 8 }}
+{{- end }}
+ lifecycle:
+ preStop:
+ exec:
+ command: ["sleep", "5"]
+ resources:
+ {{- toYaml .Values.resources | nindent 10 }}
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 3456
+ initialDelaySeconds: 30
+ periodSeconds: 10
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /healthz
+ port: 3456
+ initialDelaySeconds: 30
+ periodSeconds: 5
+ timeoutSeconds: 5
+{{- if .Values.dex.enabled }}
+ envFrom:
+ - secretRef:
+ name: {{ .Values.dex.existingSecret }}
+{{- end }}
+{{- $pluginsConfig := include "dot-ai.pluginsConfig" . -}}
+{{- $mcpServersConfig := include "dot-ai.mcpServersConfig" . -}}
+{{- if or (and $pluginsConfig (ne $pluginsConfig "[]")) (and $mcpServersConfig (ne $mcpServersConfig "[]")) }}
+ volumeMounts:
+{{- if and $pluginsConfig (ne $pluginsConfig "[]") }}
+ - name: plugins-config
+ mountPath: /etc/dot-ai
+ readOnly: true
+{{- end }}
+{{- if and $mcpServersConfig (ne $mcpServersConfig "[]") }}
+ - name: mcp-servers-config
+ mountPath: /etc/dot-ai-mcp
+ readOnly: true
+{{- end }}
+{{- end }}
+{{- if or (and $pluginsConfig (ne $pluginsConfig "[]")) (and $mcpServersConfig (ne $mcpServersConfig "[]")) }}
+ volumes:
+{{- if and $pluginsConfig (ne $pluginsConfig "[]") }}
+ - name: plugins-config
+ configMap:
+ name: {{ include "dot-ai.fullname" . }}-plugins
+{{- end }}
+{{- if and $mcpServersConfig (ne $mcpServersConfig "[]") }}
+ - name: mcp-servers-config
+ configMap:
+ name: {{ include "dot-ai.fullname" . }}-mcp-servers
+{{- end }}
+{{- end }}
+ terminationGracePeriodSeconds: 30
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/dex-config.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/dex-config.yaml
new file mode 100644
index 0000000..a10bb61
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/dex-config.yaml
@@ -0,0 +1,55 @@
+{{/*
+Dex configuration β credentials are externalized to a user-managed Secret.
+
+The user creates a Secret (referenced by .Values.dex.existingSecret) containing:
+ - DEX_CLIENT_SECRET: OAuth2 client secret for the dot-ai static client
+ - DOT_AI_JWT_SECRET: JWT signing key for MCP server tokens
+
+The admin bcrypt hash is provided directly via .Values.dex.adminPasswordHash.
+
+This template only creates the Dex config.yaml Secret β no credentials are generated.
+*/}}
+{{- if .Values.dex.enabled }}
+{{- $_ := required "dex.existingSecret is required when dex.enabled=true. Create a Secret with DEX_CLIENT_SECRET and DOT_AI_JWT_SECRET keys, then set dex.existingSecret to its name." .Values.dex.existingSecret }}
+{{- $_ := required "dex.adminPasswordHash is required when dex.enabled=true. Provide a bcrypt hash for the admin@dot-ai.local static password." .Values.dex.adminPasswordHash }}
+{{- $dexExternalUrl := required "Dex issuer URL is required. Set .Values.dex.externalUrl or configure ingress/gateway hostnames." (include "dot-ai.dexExternalUrl" . | trim) }}
+{{- $dotAiExternalUrl := required "dot-ai external URL is required. Set .Values.externalUrl or configure ingress/gateway hostnames." (include "dot-ai.externalUrl" . | trim) }}
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ default "dex" .Values.dex.configSecret.name }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+type: Opaque
+stringData:
+ config.yaml: |
+ issuer: {{ $dexExternalUrl }}
+ frontend:
+ dir: /srv/dex/web
+ issuer: "DevOps AI Toolkit"
+ storage:
+ type: kubernetes
+ config:
+ inCluster: true
+ enablePasswordDB: true
+ oauth2:
+ skipApprovalScreen: true
+ responseTypes:
+ - code
+ staticClients:
+ - id: dot-ai
+ name: "dot-ai MCP Server"
+ secretEnv: DEX_CLIENT_SECRET
+ redirectURIs:
+ - "{{ $dotAiExternalUrl }}/callback"
+ staticPasswords:
+ - email: "admin@dot-ai.local"
+ hash: {{ .Values.dex.adminPasswordHash | quote }}
+ username: "admin"
+ userID: "00000000-0000-0000-0000-000000000001"
+ {{- if .Values.dex.connectors }}
+ connectors:
+ {{- toYaml .Values.dex.connectors | nindent 6 }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/dex-httproute.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/dex-httproute.yaml
new file mode 100644
index 0000000..d3669a2
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/dex-httproute.yaml
@@ -0,0 +1,37 @@
+{{/*
+Dex HTTPRoute β automatically created when Gateway API is used.
+Derives hostname from the main gateway listeners (prepends "dex." prefix).
+*/}}
+{{- if and .Values.dex.enabled (or .Values.gateway.name .Values.gateway.create) }}
+{{- $dexHost := include "dot-ai.dexExternalHost" . }}
+{{- if $dexHost }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-dex
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ parentRefs:
+ - name: {{ if .Values.gateway.name }}{{ .Values.gateway.name }}{{ else }}{{ include "dot-ai.fullname" . }}-http{{ end }}
+ kind: Gateway
+ {{- if and .Values.gateway.name .Values.gateway.namespace }}
+ namespace: {{ .Values.gateway.namespace }}
+ {{- end }}
+ hostnames:
+ - {{ $dexHost }}
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /
+ backendRefs:
+ - name: {{ .Release.Name }}-dex
+ port: 5556
+{{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/dex-ingress.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/dex-ingress.yaml
new file mode 100644
index 0000000..a222ae6
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/dex-ingress.yaml
@@ -0,0 +1,41 @@
+{{/*
+Dex Ingress β automatically created when the main ingress is enabled.
+Derives host from the main ingress host (prepends "dex." prefix).
+*/}}
+{{- if and .Values.dex.enabled .Values.ingress.enabled }}
+{{- $dexHost := include "dot-ai.dexExternalHost" . }}
+{{- if $dexHost }}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-dex
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ {{- if .Values.ingress.className }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls.enabled }}
+ tls:
+ - hosts:
+ - {{ $dexHost }}
+ secretName: {{ include "dot-ai.fullname" . }}-dex-tls
+ {{- end }}
+ rules:
+ - host: {{ $dexHost }}
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: {{ .Release.Name }}-dex
+ port:
+ number: 5556
+{{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/gateway.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/gateway.yaml
new file mode 100644
index 0000000..eb5e1de
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/gateway.yaml
@@ -0,0 +1,66 @@
+{{- if .Values.gateway.create }}
+{{- if not .Values.gateway.className }}
+{{- fail "gateway.className is required when gateway.create is true" }}
+{{- end }}
+{{- if not (or .Values.gateway.listeners.http.enabled .Values.gateway.listeners.https.enabled) }}
+{{- fail "At least one listener (http or https) must be enabled when gateway.create is true" }}
+{{- end }}
+{{- if .Values.gateway.name }}
+{{- fail "gateway.name and gateway.create cannot both be set; disable gateway.create when using an existing gateway.name" }}
+{{- end }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-http
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" .Values.gateway.annotations) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ gatewayClassName: {{ .Values.gateway.className }}
+ listeners:
+ {{- if .Values.gateway.listeners.http.enabled }}
+ - name: http
+ protocol: HTTP
+ port: 80
+ {{- if .Values.gateway.listeners.http.hostname }}
+ hostname: {{ .Values.gateway.listeners.http.hostname }}
+ {{- end }}
+ allowedRoutes:
+ namespaces:
+ from: Same
+ {{- end }}
+ {{- if .Values.gateway.listeners.https.enabled }}
+ - name: https
+ protocol: HTTPS
+ port: 443
+ {{- if .Values.gateway.listeners.https.hostname }}
+ hostname: {{ .Values.gateway.listeners.https.hostname }}
+ {{- end }}
+ tls:
+ mode: Terminate
+ {{- if .Values.gateway.listeners.https.certificateRefs }}
+ certificateRefs:
+ {{- range .Values.gateway.listeners.https.certificateRefs }}
+ - kind: {{ .kind | default "Secret" }}
+ name: {{ .name }}
+ {{- if .group }}
+ group: {{ .group }}
+ {{- end }}
+ {{- if .namespace }}
+ namespace: {{ .namespace }}
+ {{- end }}
+ {{- end }}
+ {{- else }}
+ certificateRefs:
+ - kind: Secret
+ name: {{ .Values.gateway.listeners.https.secretName | default (printf "%s-tls" (include "dot-ai.fullname" .)) }}
+ {{- end }}
+ allowedRoutes:
+ namespaces:
+ from: Same
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/httproute.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/httproute.yaml
new file mode 100644
index 0000000..e8e12a3
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/httproute.yaml
@@ -0,0 +1,46 @@
+{{- if or .Values.gateway.name .Values.gateway.create }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ include "dot-ai.fullname" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ parentRefs:
+ - name: {{ if .Values.gateway.name }}{{ .Values.gateway.name }}{{ else }}{{ include "dot-ai.fullname" . }}-http{{ end }}
+ kind: Gateway
+ {{- if and .Values.gateway.name .Values.gateway.namespace }}
+ namespace: {{ .Values.gateway.namespace }}
+ {{- end }}
+ {{- if or .Values.gateway.listeners.http.hostname .Values.gateway.listeners.https.hostname }}
+ hostnames:
+ {{- if .Values.gateway.listeners.http.hostname }}
+ - {{ .Values.gateway.listeners.http.hostname }}
+ {{- end }}
+ {{- if and .Values.gateway.listeners.https.hostname (ne .Values.gateway.listeners.http.hostname .Values.gateway.listeners.https.hostname) }}
+ - {{ .Values.gateway.listeners.https.hostname }}
+ {{- end }}
+ {{- end }}
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /
+ backendRefs:
+ - name: {{ include "dot-ai.fullname" . }}
+ port: 3456
+ {{- if and .Values.gateway.timeouts (or .Values.gateway.timeouts.request .Values.gateway.timeouts.backendRequest) }}
+ timeouts:
+ {{- if .Values.gateway.timeouts.request }}
+ request: {{ .Values.gateway.timeouts.request }}
+ {{- end }}
+ {{- if .Values.gateway.timeouts.backendRequest }}
+ backendRequest: {{ .Values.gateway.timeouts.backendRequest }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/ingress.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/ingress.yaml
new file mode 100644
index 0000000..ac2a5ed
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/ingress.yaml
@@ -0,0 +1,42 @@
+{{- if and .Values.ingress.enabled (or .Values.gateway.name .Values.gateway.create) }}
+{{- fail "Cannot enable both ingress.enabled and Gateway API usage (gateway.name or gateway.create). Please choose one ingress method." }}
+{{- end }}
+{{- if .Values.ingress.enabled }}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "dot-ai.fullname" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" .Values.ingress.annotations) -}}
+ {{- if or $annotations .Values.ingress.tls.clusterIssuer }}
+ annotations:
+ {{- if $annotations }}
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+ {{- if .Values.ingress.tls.clusterIssuer }}
+ cert-manager.io/cluster-issuer: {{ .Values.ingress.tls.clusterIssuer | quote }}
+ {{- end }}
+ {{- end }}
+spec:
+ {{- if .Values.ingress.className }}
+ ingressClassName: {{ .Values.ingress.className }}
+ {{- end }}
+ {{- if .Values.ingress.tls.enabled }}
+ tls:
+ - hosts:
+ - {{ .Values.ingress.host }}
+ secretName: {{ .Values.ingress.tls.secretName | default (printf "%s-tls" (include "dot-ai.fullname" .)) }}
+ {{- end }}
+ rules:
+ - host: {{ .Values.ingress.host }}
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: {{ include "dot-ai.fullname" . }}
+ port:
+ number: 3456
+{{- end }}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/local-embeddings.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/local-embeddings.yaml
new file mode 100644
index 0000000..3ff48c0
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/local-embeddings.yaml
@@ -0,0 +1,93 @@
+{{/*
+Local Embedding Service (PRD #384)
+Deploys HuggingFace Text Embeddings Inference (TEI) as an in-cluster embedding service.
+Provides zero-config semantic search for users without embedding API keys.
+*/}}
+{{- if .Values.localEmbeddings.enabled }}
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ .Release.Name }}-local-embeddings
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ app.kubernetes.io/component: local-embeddings
+ app.kubernetes.io/part-of: dot-ai
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: local-embeddings
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: local-embeddings
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ {{- $podAnnotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $podAnnotations }}
+ annotations:
+ {{- $podAnnotations | nindent 8 }}
+ {{- end }}
+ spec:
+ containers:
+ - name: local-embeddings
+ image: "{{ .Values.localEmbeddings.image.repository }}:{{ .Values.localEmbeddings.image.tag }}"
+ imagePullPolicy: IfNotPresent
+ args:
+ - "--model-id"
+ - {{ .Values.localEmbeddings.model | quote }}
+ - "--port"
+ - "80"
+ ports:
+ - containerPort: 80
+ name: http
+ protocol: TCP
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: 80
+ initialDelaySeconds: 30
+ periodSeconds: 10
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /health
+ port: 80
+ initialDelaySeconds: 15
+ periodSeconds: 5
+ timeoutSeconds: 5
+ {{- with .Values.localEmbeddings.resources }}
+ resources:
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ terminationGracePeriodSeconds: 30
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Release.Name }}-local-embeddings
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ app.kubernetes.io/component: local-embeddings
+ app.kubernetes.io/part-of: dot-ai
+ {{- $svcAnnotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $svcAnnotations }}
+ annotations:
+ {{- $svcAnnotations | nindent 4 }}
+ {{- end }}
+spec:
+ type: ClusterIP
+ selector:
+ app.kubernetes.io/name: local-embeddings
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ ports:
+ - port: 80
+ targetPort: 80
+ protocol: TCP
+ name: http
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/mcp-servers-configmap.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/mcp-servers-configmap.yaml
new file mode 100644
index 0000000..8740c37
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/mcp-servers-configmap.yaml
@@ -0,0 +1,22 @@
+{{/*
+MCP Servers Configuration ConfigMap (PRD #358)
+Contains JSON array of enabled MCP servers for discovery by dot-ai.
+Mounted at /etc/dot-ai-mcp/mcp-servers.json
+*/}}
+{{- $mcpServersConfig := include "dot-ai.mcpServersConfig" . -}}
+{{- if and $mcpServersConfig (ne $mcpServersConfig "[]") }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-mcp-servers
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+data:
+ mcp-servers.json: |
+ {{ $mcpServersConfig | nindent 4 | trim }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/plugin-configmap.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/plugin-configmap.yaml
new file mode 100644
index 0000000..6e3d2ad
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/plugin-configmap.yaml
@@ -0,0 +1,22 @@
+{{/*
+Plugin Configuration ConfigMap (PRD #343)
+Contains JSON array of enabled plugins for discovery by MCP server.
+Mounted at /etc/dot-ai/plugins.json
+*/}}
+{{- $pluginsConfig := include "dot-ai.pluginsConfig" . -}}
+{{- if and $pluginsConfig (ne $pluginsConfig "[]") }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-plugins
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+data:
+ plugins.json: |
+ {{ $pluginsConfig | nindent 4 | trim }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/plugin-deployment.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/plugin-deployment.yaml
new file mode 100644
index 0000000..7924385
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/plugin-deployment.yaml
@@ -0,0 +1,114 @@
+{{/*
+Plugin Deployments and Services (PRD #343)
+Creates Deployment + Service for plugins with image configuration (deployed mode).
+External plugins (endpoint only) are not deployed - just registered in DOT_AI_PLUGINS_CONFIG.
+*/}}
+{{- range $name, $config := .Values.plugins }}
+{{- if and $config.enabled $config.image }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ $.Release.Name }}-{{ $name }}
+ labels:
+ {{- include "dot-ai.labels" $ | nindent 4 }}
+ app.kubernetes.io/component: plugin
+ app.kubernetes.io/part-of: dot-ai
+ {{- $annotations := include "dot-ai.annotations" (dict "global" $.Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: {{ $name }}
+ app.kubernetes.io/instance: {{ $.Release.Name }}
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: {{ $name }}
+ app.kubernetes.io/instance: {{ $.Release.Name }}
+ {{- $podAnnotations := include "dot-ai.annotations" (dict "global" $.Values.annotations "local" nil) -}}
+ {{- if $podAnnotations }}
+ annotations:
+ {{- $podAnnotations | nindent 8 }}
+ {{- end }}
+ spec:
+ {{- if eq $name "agentic-tools" }}
+ {{- /* agentic-tools uses the existing dot-ai ServiceAccount for K8s RBAC */}}
+ serviceAccountName: {{ include "dot-ai.serviceAccountName" $ }}
+ {{- else if $config.serviceAccountName }}
+ serviceAccountName: {{ $config.serviceAccountName }}
+ {{- end }}
+ containers:
+ - name: {{ $name }}
+ image: "{{ $config.image.repository }}:{{ $config.image.tag }}"
+ imagePullPolicy: {{ $config.image.pullPolicy | default "IfNotPresent" }}
+ ports:
+ - containerPort: {{ $config.port }}
+ name: http
+ protocol: TCP
+ lifecycle:
+ preStop:
+ exec:
+ command: ["sleep", "5"]
+ livenessProbe:
+ httpGet:
+ path: /health
+ port: {{ $config.port }}
+ initialDelaySeconds: 10
+ periodSeconds: 10
+ timeoutSeconds: 5
+ readinessProbe:
+ httpGet:
+ path: /ready
+ port: {{ $config.port }}
+ initialDelaySeconds: 30
+ periodSeconds: 5
+ timeoutSeconds: 5
+ env:
+ {{- if eq $name "agentic-tools" }}
+ {{- /* PRD #359: agentic-tools needs Qdrant URL for vector operations */}}
+ - name: QDRANT_URL
+ value: {{ if $.Values.qdrant.enabled }}"http://{{ $.Release.Name }}-qdrant.{{ $.Release.Namespace }}.svc.cluster.local:6333"{{ else }}{{ $.Values.qdrant.external.url | quote }}{{ end }}
+ {{- end }}
+ {{- with $config.env }}
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ {{- with $config.envFrom }}
+ envFrom:
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ {{- with $config.resources }}
+ resources:
+ {{- toYaml . | nindent 10 }}
+ {{- end }}
+ terminationGracePeriodSeconds: 30
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ $.Release.Name }}-{{ $name }}
+ labels:
+ {{- include "dot-ai.labels" $ | nindent 4 }}
+ app.kubernetes.io/component: plugin
+ app.kubernetes.io/part-of: dot-ai
+ {{- $svcAnnotations := include "dot-ai.annotations" (dict "global" $.Values.annotations "local" nil) -}}
+ {{- if $svcAnnotations }}
+ annotations:
+ {{- $svcAnnotations | nindent 4 }}
+ {{- end }}
+spec:
+ type: ClusterIP
+ selector:
+ app.kubernetes.io/name: {{ $name }}
+ app.kubernetes.io/instance: {{ $.Release.Name }}
+ ports:
+ - port: {{ $config.port }}
+ targetPort: {{ $config.port }}
+ protocol: TCP
+ name: http
+{{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/rbac-enforcement.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/rbac-enforcement.yaml
new file mode 100644
index 0000000..4cf61c2
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/rbac-enforcement.yaml
@@ -0,0 +1,100 @@
+{{- if .Values.rbac.enforcement.enabled }}
+# RBAC Enforcement (PRD #392)
+# Pre-built ClusterRoles for dot-ai tool-level authorization.
+# Admins create ClusterRoleBindings/RoleBindings to assign these roles to users.
+# Uses virtual API group "dot-ai.devopstoolkit.ai" β no CRDs required.
+
+# dotai-viewer: Read-only cluster visibility
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: dotai-viewer
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ dot-ai.devopstoolkit.ai/rbac-role: "viewer"
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+rules:
+ - apiGroups: ["dot-ai.devopstoolkit.ai"]
+ resources: ["tools"]
+ verbs: ["execute"]
+---
+# dotai-operator: Day-2 operations (plan + apply)
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: dotai-operator
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ dot-ai.devopstoolkit.ai/rbac-role: "operator"
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+rules:
+ - apiGroups: ["dot-ai.devopstoolkit.ai"]
+ resources: ["tools"]
+ verbs: ["execute", "apply"]
+---
+# dotai-admin: Full access including user management
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: dotai-admin
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ dot-ai.devopstoolkit.ai/rbac-role: "admin"
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+rules:
+ - apiGroups: ["dot-ai.devopstoolkit.ai"]
+ resources: ["tools"]
+ verbs: ["execute", "apply"]
+ - apiGroups: ["dot-ai.devopstoolkit.ai"]
+ resources: ["users"]
+ verbs: ["execute", "apply"]
+---
+# dotai-auth-checker: Allows dot-ai ServiceAccount to create SubjectAccessReviews
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-auth-checker
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+rules:
+ - apiGroups: ["authorization.k8s.io"]
+ resources: ["subjectaccessreviews"]
+ verbs: ["create"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{ include "dot-ai.fullname" . }}-auth-checker
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ include "dot-ai.fullname" . }}-auth-checker
+subjects:
+ - kind: ServiceAccount
+ name: {{ include "dot-ai.serviceAccountName" . }}
+ namespace: {{ .Release.Namespace }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/secret.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/secret.yaml
new file mode 100644
index 0000000..08f2f6b
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/secret.yaml
@@ -0,0 +1,53 @@
+{{- if or .Values.secrets.auth.token .Values.secrets.anthropic.apiKey .Values.secrets.openai.apiKey .Values.secrets.google.apiKey .Values.secrets.xai.apiKey .Values.secrets.moonshot.apiKey .Values.secrets.alibaba.apiKey .Values.secrets.customLlm.apiKey .Values.secrets.customEmbeddings.apiKey .Values.secrets.githubApp.enabled -}}
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ .Values.secrets.name }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+type: Opaque
+stringData:
+ {{- if .Values.secrets.auth.token }}
+ {{ .Values.secrets.auth.keyName }}: {{ .Values.secrets.auth.token | quote }}
+ {{- end }}
+ {{- if .Values.secrets.anthropic.apiKey }}
+ {{ .Values.secrets.anthropic.keyName }}: {{ .Values.secrets.anthropic.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.openai.apiKey }}
+ {{ .Values.secrets.openai.keyName }}: {{ .Values.secrets.openai.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.google.apiKey }}
+ {{ .Values.secrets.google.keyName }}: {{ .Values.secrets.google.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.xai.apiKey }}
+ {{ .Values.secrets.xai.keyName }}: {{ .Values.secrets.xai.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.moonshot.apiKey }}
+ {{ .Values.secrets.moonshot.keyName }}: {{ .Values.secrets.moonshot.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.alibaba.apiKey }}
+ {{ .Values.secrets.alibaba.keyName }}: {{ .Values.secrets.alibaba.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.customLlm.apiKey }}
+ {{ .Values.secrets.customLlm.keyName }}: {{ .Values.secrets.customLlm.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.customEmbeddings.apiKey }}
+ {{ .Values.secrets.customEmbeddings.keyName }}: {{ .Values.secrets.customEmbeddings.apiKey | quote }}
+ {{- end }}
+ {{- if .Values.secrets.githubApp.enabled }}
+ {{- if not (and .Values.secrets.githubApp.appId .Values.secrets.githubApp.privateKey) }}
+ {{- fail "secrets.githubApp.enabled is true but secrets.githubApp.appId or secrets.githubApp.privateKey is missing" }}
+ {{- end }}
+ github-app-enabled: "true"
+ github-app-id: {{ .Values.secrets.githubApp.appId | quote }}
+ github-app-private-key: {{ .Values.secrets.githubApp.privateKey | quote }}
+ {{- if .Values.secrets.githubApp.installationId }}
+ github-app-installation-id: {{ .Values.secrets.githubApp.installationId | quote }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/service.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/service.yaml
new file mode 100644
index 0000000..1570ef4
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/service.yaml
@@ -0,0 +1,20 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "dot-ai.fullname" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+spec:
+ type: ClusterIP
+ selector:
+ {{- include "dot-ai.selectorLabels" . | nindent 4 }}
+ ports:
+ - port: 3456
+ targetPort: 3456
+ protocol: TCP
+ name: http
diff --git a/charts/dot-ai-stack/charts/dot-ai/templates/serviceaccount.yaml b/charts/dot-ai-stack/charts/dot-ai/templates/serviceaccount.yaml
new file mode 100644
index 0000000..59db00b
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/templates/serviceaccount.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "dot-ai.serviceAccountName" . }}
+ labels:
+ {{- include "dot-ai.labels" . | nindent 4 }}
+ {{- $annotations := include "dot-ai.annotations" (dict "global" .Values.annotations "local" nil) -}}
+ {{- if $annotations }}
+ annotations:
+ {{- $annotations | nindent 4 }}
+ {{- end }}
+{{- end }}
\ No newline at end of file
diff --git a/charts/dot-ai-stack/charts/dot-ai/values.yaml b/charts/dot-ai-stack/charts/dot-ai/values.yaml
new file mode 100644
index 0000000..9abf205
--- /dev/null
+++ b/charts/dot-ai-stack/charts/dot-ai/values.yaml
@@ -0,0 +1,401 @@
+# Global annotations applied to ALL Kubernetes resources
+# Use for tools like Reloader, compliance requirements, or consistent metadata
+annotations: {}
+# Example - uncomment and modify:
+# annotations:
+# reloader.stakater.com/auto: "true"
+# company.com/managed-by: "platform-team"
+
+# Application image configuration
+image:
+ repository: ghcr.io/vfarcic/dot-ai # Container image repository
+ tag: "1.12.0" # Container image tag - set by CI pipeline during release
+
+# Resource configuration
+resources:
+ requests:
+ memory: "512Mi" # Minimum memory required
+ cpu: "200m" # Minimum CPU required
+ limits:
+ memory: "2Gi" # Maximum memory allowed
+ cpu: "1000m" # Maximum CPU allowed
+
+# Secrets configuration
+secrets:
+ name: dot-ai-secrets # Name of the Kubernetes Secret resource
+ auth:
+ keyName: auth-token # Key name within the secret for Bearer token auth
+ token: "" # Auth token value (only needed if chart should create the secret)
+ anthropic:
+ keyName: anthropic-api-key # Key name within the secret
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ openai:
+ keyName: openai-api-key # Key name within the secret
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ google:
+ keyName: google-api-key # Key name within the secret
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ xai:
+ keyName: xai-api-key # Key name within the secret
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ moonshot:
+ keyName: moonshot-api-key # Key name within the secret (PRD #237: Kimi K2)
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ alibaba:
+ keyName: alibaba-api-key # Key name within the secret (PRD #382: Alibaba Qwen 3.5 Plus)
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ customLlm:
+ keyName: custom-llm-api-key # Key name within the secret for custom LLM endpoint
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ customEmbeddings:
+ keyName: custom-embeddings-api-key # Key name within the secret for custom embeddings endpoint
+ apiKey: "" # API key value (only needed if chart should create the secret)
+ # GitHub App authentication for git operations (PRD #362)
+ # Note: PAT token uses DOT_AI_GIT_TOKEN env var configured via extraEnv
+ githubApp:
+ enabled: false # Enable GitHub App authentication
+ appId: "" # GitHub App ID
+ privateKey: "" # GitHub App private key (PEM format)
+ installationId: "" # GitHub App installation ID (optional, auto-detected if not provided)
+
+# ServiceAccount configuration
+serviceAccount:
+ create: true # Create a ServiceAccount
+ name: "" # ServiceAccount name override (generated if empty)
+
+# Ingress configuration
+# NOTE: Mutually exclusive with gateway.enabled - only one can be enabled
+ingress:
+ enabled: false # Create Ingress resource
+ className: nginx # Ingress class name
+ host: dot-ai.127.0.0.1.nip.io # Ingress hostname
+ # Annotations required for HTTP transport with SSE (Server-Sent Events)
+ # If using different className, update annotations for your ingress controller:
+ # - Traefik: traefik.ingress.kubernetes.io/service.sticky.cookie.httponly: "true"
+ # - HAProxy: haproxy.org/timeout-http-request: "3600s"
+ # - AWS ALB: alb.ingress.kubernetes.io/target-group-attributes: idle_timeout.timeout_seconds=3600
+ annotations:
+ nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" # Allow long-running SSE connections
+ nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" # Allow long-running SSE connections
+ nginx.ingress.kubernetes.io/proxy-buffering: "off" # Disable buffering for real-time streaming
+ nginx.ingress.kubernetes.io/proxy-request-buffering: "off" # Disable request buffering for real-time streaming
+ tls:
+ enabled: false # Enable TLS/HTTPS
+ secretName: "" # TLS secret name (generated if empty when enabled)
+ clusterIssuer: "" # cert-manager ClusterIssuer name (e.g., "letsencrypt")
+
+# Gateway API configuration (Kubernetes 1.26+)
+# NOTE: Mutually exclusive with ingress.enabled - only one can be enabled
+# Requires Gateway API CRDs pre-installed in cluster
+#
+# RECOMMENDED: Reference existing Gateway (platform team manages Gateway infrastructure)
+# Example:
+# gateway:
+# name: "cluster-gateway" # Reference existing Gateway
+# namespace: "gateway-system" # Optional: Gateway namespace (if in different namespace)
+#
+# ALTERNATIVE: Create Gateway for development/testing (with -http suffix to prevent naming collisions)
+# Example:
+# gateway:
+# create: true # Create Gateway resource
+# className: "istio" # GatewayClass name required when create=true
+gateway:
+ # Reference mode: Specify name of existing Gateway resource (RECOMMENDED)
+ # When set, HTTPRoute will reference this Gateway. Gateway must exist before deployment.
+ # Leave empty when using create mode.
+ name: "" # Gateway name to reference (e.g., "cluster-gateway")
+
+ # Optional: Gateway namespace for cross-namespace references
+ # Only used when 'name' is set. Requires ReferenceGrant in Gateway namespace.
+ # Leave empty if Gateway is in same namespace as this chart.
+ namespace: "" # Gateway namespace (e.g., "gateway-system")
+
+ # Creation mode: Create a new Gateway resource (for development/testing only)
+ # When true, creates Gateway with name "-http" to prevent kGateway naming collisions.
+ # Requires 'className' to be set. Not recommended for production (use reference mode instead).
+ create: false # Create Gateway resource (false = reference mode)
+
+ # GatewayClass name - REQUIRED when create=true, ignored when referencing existing Gateway
+ className: "" # GatewayClass name (e.g., "istio", "envoy-gateway", "gke-l7-global-external-managed")
+
+ # Annotations for integration with external-dns, cert-manager, etc.
+ # Only used when create=true. For existing Gateways, configure annotations on the Gateway resource directly.
+ # Example for external-dns: external-dns.alpha.kubernetes.io/hostname: "dot-ai.example.com"
+ annotations: {}
+
+ # Listener configuration - only used when create=true
+ # For existing Gateways, listeners are already configured on the Gateway resource.
+ listeners:
+ http:
+ enabled: true # Enable HTTP listener on port 80
+ hostname: "" # Optional: hostname for HTTP listener (e.g., "dot-ai.example.com")
+ https:
+ enabled: false # Enable HTTPS listener on port 443
+ hostname: "" # Optional: hostname for HTTPS listener (e.g., "dot-ai.example.com")
+ secretName: "" # TLS secret name (generated if empty: -tls)
+ # Optional: certificateRefs for advanced TLS configuration
+ # Useful for cert-manager integration or cross-namespace secrets (requires ReferenceGrant)
+ # certificateRefs:
+ # - kind: Secret
+ # name: dot-ai-tls
+ # # Optional: group (defaults to core for Secret)
+ # # Optional: namespace (for cross-namespace references, requires ReferenceGrant)
+ certificateRefs: []
+
+ # Timeouts for SSE streaming support (GEP-1742)
+ # Applied to HTTPRoute rules for both reference and create modes
+ timeouts:
+ request: "3600s" # Request timeout (supports SSE streaming)
+ backendRequest: "3600s" # Backend request timeout
+
+# AI Provider configuration
+ai:
+ provider: anthropic # AI provider type (anthropic, anthropic_opus, anthropic_haiku, openai, google, google_flash, host, kimi, xai, amazon_bedrock)
+ model: "" # Optional: model override (e.g., "llama3.1:70b", "gpt-4o")
+
+ # Custom endpoint configuration for self-hosted or alternative SaaS providers (PRD #194)
+ customEndpoint:
+ enabled: false # Enable custom endpoint
+ baseURL: "" # Custom LLM endpoint URL - MUST include /v1 suffix for OpenAI-compatible APIs (e.g., "http://ollama-service:11434/v1")
+ embeddingsBaseURL: "" # Optional: custom embeddings endpoint URL (if different from LLM endpoint, also requires /v1 suffix)
+ embeddingsModel: "" # Optional: custom embeddings model name (e.g., "nomic-embed-text" for Ollama, defaults to "text-embedding-3-small" for OpenAI)
+ embeddingsDimensions: "" # Optional: custom embeddings dimensions (e.g., "768" for nomic-embed-text, defaults to "1536" for OpenAI)
+
+ # Examples (commented out):
+ # Example 1: Ollama (self-hosted) - IMPORTANT: Include /v1 suffix
+ # ai:
+ # provider: openai
+ # model: "llama3.1:70b"
+ # customEndpoint:
+ # enabled: true
+ # baseURL: "http://ollama-service:11434/v1" # /v1 suffix is REQUIRED
+ #
+ # Example 2: Azure OpenAI (SaaS)
+ # ai:
+ # provider: openai
+ # model: "gpt-4o"
+ # customEndpoint:
+ # enabled: true
+ # baseURL: "https://YOUR_RESOURCE.openai.azure.com/openai/deployments/YOUR_DEPLOYMENT"
+ #
+ # Example 3: vLLM (self-hosted) - IMPORTANT: Include /v1 suffix
+ # ai:
+ # provider: openai
+ # model: "meta-llama/Llama-3.1-70B-Instruct"
+ # customEndpoint:
+ # enabled: true
+ # baseURL: "http://vllm-service:8000/v1" # /v1 suffix is REQUIRED
+ #
+ # Note: OpenAI-compatible endpoints (Ollama, vLLM, LocalAI) REQUIRE the /v1 suffix.
+ # Without it, API calls will fail with 404 Not Found errors.
+ # Custom endpoints must support OpenAI-compatible API and models must
+ # support 8K+ output tokens for reliable YAML generation.
+
+# Web UI integration (PRD #317: Query Visualization)
+# When configured, query tool responses include a visualizationUrl that opens
+# rich visualizations in the Web UI (resource topology, relationships, health)
+webUI:
+ baseUrl: "" # Web UI base URL (e.g., "https://dot-ai-ui.example.com")
+ # When set, query responses include visualizationUrl field
+ # Format: {baseUrl}/v/{sessionId}
+
+# Additional environment variables (optional)
+# Use this to add any custom environment variables to the MCP server
+# Example use cases: tracing configuration, custom integrations, Git authentication
+#
+# Git Authentication for pushToGit stage (PRD #395):
+# To enable GitOps workflows, configure one of the following:
+#
+# Option 1: Personal Access Token (PAT)
+# extraEnv:
+# - name: DOT_AI_GIT_TOKEN
+# valueFrom:
+# secretKeyRef:
+# name: dot-ai-secrets
+# key: git-token
+#
+# Less secure inline alternative:
+# extraEnv:
+# - name: DOT_AI_GIT_TOKEN
+# value: "ghp_your_token_here"
+#
+# Option 2: GitHub App (recommended for production)
+# - Set secrets.githubApp.enabled: true
+# - Configure secrets.githubApp.appId and secrets.githubApp.privateKey
+#
+# Required token scopes:
+# - repo (for private repositories)
+# - public_repo (for public repositories)
+#
+extraEnv: []
+ # OpenTelemetry tracing:
+ # - name: OTEL_TRACING_ENABLED
+ # value: "true"
+ # - name: OTEL_EXPORTER_OTLP_ENDPOINT
+ # value: "http://jaeger-collector:4318/v1/traces"
+ # - name: OTEL_SERVICE_NAME
+ # value: "dot-ai-mcp-production"
+ #
+ # Telemetry (PRD #329): Anonymous usage analytics, enabled by default
+ # - name: DOT_AI_TELEMETRY
+ # value: "false" # Set to "false" to disable telemetry
+ # - name: DOT_AI_POSTHOG_HOST
+ # value: "https://your-posthog.example.com" # Self-hosted PostHog (optional)
+
+# Qdrant Vector Database
+qdrant:
+ enabled: true # Deploy Qdrant as dependency (false = use external)
+ image:
+ repository: qdrant/qdrant # Qdrant image repository
+ tag: v1.15.5 # Qdrant image tag
+ external:
+ url: "" # External Qdrant URL (required when enabled=false)
+ apiKey: "" # External Qdrant API key (optional)
+
+# Local Embedding Service (PRD #384)
+# Deploys HuggingFace Text Embeddings Inference (TEI) as an in-cluster embedding service.
+# When enabled and no embedding API keys are configured (OpenAI or custom), dot-ai
+# automatically connects to this service via CUSTOM_EMBEDDINGS_BASE_URL.
+# Existing users with OpenAI API keys or custom embedding endpoints are unaffected.
+localEmbeddings:
+ enabled: false # Deploy local embedding service (set true if not using cloud embedding API keys)
+ image:
+ repository: ghcr.io/huggingface/text-embeddings-inference
+ tag: "cpu-latest"
+ model: "sentence-transformers/all-MiniLM-L6-v2" # Embedding model (384 dimensions)
+ dimensions: 384 # Must match the model's output dimensions
+ resources:
+ requests:
+ cpu: "250m"
+ memory: "256Mi"
+ limits:
+ cpu: "1"
+ memory: "512Mi"
+
+# Dex OIDC Provider (PRD #380)
+# Deploys Dex as the identity provider for OAuth2/OIDC authentication.
+# MCP clients authenticate via Dex and receive JWT access tokens.
+# Routing (Ingress/Gateway) is automatically derived from the main server's config.
+# NOTE: OAuth requires HTTPS. Enable Dex only when HTTPS is configured
+# (via ingress.tls, gateway HTTPS listener, or an upstream reverse proxy/load balancer).
+# Without Dex, authentication falls back to static token (DOT_AI_AUTH_TOKEN).
+# RBAC Enforcement (PRD #392)
+# When enabled, OAuth-authenticated users are checked against Kubernetes RBAC
+# before tool execution. Token users always bypass RBAC.
+# Requires dex.enabled=true (OAuth must be configured for RBAC to have effect).
+rbac:
+ enforcement:
+ enabled: false # Enable RBAC enforcement (default deny for OAuth users)
+
+dex:
+ enabled: false # Deploy Dex for OAuth authentication (requires HTTPS)
+ # REQUIRED when enabled: name of a pre-existing Secret containing:
+ # DEX_CLIENT_SECRET β OAuth2 client secret (shared between MCP server and Dex)
+ # DOT_AI_JWT_SECRET β JWT signing key for MCP server tokens
+ # Generate with:
+ # kubectl create secret generic dot-ai-dex-auth --namespace \
+ # --from-literal=DEX_CLIENT_SECRET="$(openssl rand -hex 32)" \
+ # --from-literal=DOT_AI_JWT_SECRET="$(openssl rand -hex 32)"
+ existingSecret: ""
+ # REQUIRED when enabled: bcrypt hash for the built-in admin@dot-ai.local password.
+ # Generate with: htpasswd -nbBC 10 "" "your-password" | cut -d: -f2
+ adminPasswordHash: ""
+ # Production IdP connectors (optional)
+ # When configured, users authenticate via their organization's identity provider.
+ # See https://dexidp.io/docs/connectors/ for available connectors.
+ connectors: []
+ # Example:
+ # connectors:
+ # - type: github
+ # id: github
+ # name: GitHub
+ # config:
+ # clientID: "xxx"
+ # clientSecret: "xxx"
+ # redirectURI: "https://dex.example.com/callback"
+ configSecret:
+ create: false
+ name: dex
+ # envFrom for the Dex pod β MUST include existingSecret so Dex reads DEX_CLIENT_SECRET.
+ # Add connector secrets after it.
+ # Example (with Google connector):
+ # envFrom:
+ # - secretRef:
+ # name: dot-ai-dex-auth # Must match existingSecret
+ # - secretRef:
+ # name: my-connector-secret
+ envFrom: []
+ grpc:
+ enabled: true # Enable gRPC API for user management (PRD #380 Task 2.5)
+ image: # Custom image with branded login page (PRD #380 Task 3.6)
+ repository: ghcr.io/vfarcic/dot-ai-dex
+ tag: "v2.44.0"
+
+# Plugin configuration (PRD #343)
+# Plugins are configured as a map, allowing users to customize defaults or add their own.
+# Two modes:
+# - Deployed: Provide `image` + `port` β chart creates Deployment + Service
+# - External: Provide `endpoint` β chart just registers it (you deploy separately)
+plugins:
+ # Built-in agentic-tools plugin - deployed by chart
+ agentic-tools:
+ enabled: true
+ image:
+ repository: ghcr.io/vfarcic/dot-ai-agentic-tools
+ tag: "1.12.0"
+ port: 8080
+ # RBAC created automatically by chart (hardcoded for agentic-tools)
+ env: [] # Additional environment variables
+ envFrom: [] # Environment from ConfigMaps/Secrets
+ resources:
+ requests:
+ memory: "128Mi"
+ cpu: "100m"
+ limits:
+ memory: "512Mi"
+ cpu: "500m"
+
+ # Example: User plugin deployed by chart
+ # my-company-tools:
+ # enabled: true
+ # image:
+ # repository: myregistry/my-tools
+ # tag: "1.12.0"
+ # port: 8082
+ # serviceAccountName: "my-tools-sa" # You create this SA with needed permissions
+ # env:
+ # - name: API_KEY
+ # valueFrom:
+ # secretKeyRef:
+ # name: my-tools-secrets
+ # key: api-key
+
+ # Example: External plugin (you deploy separately)
+ # my-external-plugin:
+ # enabled: true
+ # endpoint: "http://my-plugin.other-namespace.svc:8080"
+ # # NOTE: You are responsible for deploying this plugin and
+ # # ensuring it has appropriate Kubernetes RBAC permissions.
+
+# MCP Server Integration (PRD #358)
+# Connect to external MCP servers running in the cluster to augment
+# dot-ai tools with additional capabilities (metrics, tracing, etc.).
+#
+# MCP servers must already be deployed and accessible in-cluster.
+# dot-ai connects as an MCP client to discover and use their tools.
+# The `attachTo` field controls which dot-ai operations can use each server's tools.
+mcpServers: {}
+ # Example: Prometheus MCP server for metrics
+ # prometheus:
+ # enabled: true
+ # endpoint: "http://prometheus-mcp.monitoring.svc:3000/mcp"
+ # attachTo:
+ # - remediate
+ # - query
+ #
+ # Example: Jaeger MCP server for tracing
+ # jaeger:
+ # enabled: true
+ # endpoint: "http://jaeger-mcp.tracing.svc:3000/mcp"
+ # attachTo:
+ # - remediate
diff --git a/charts/dot-ai-stack/docs/CHANGELOG.md b/charts/dot-ai-stack/docs/CHANGELOG.md
new file mode 100644
index 0000000..1964648
--- /dev/null
+++ b/charts/dot-ai-stack/docs/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/),
+and this project adheres to [Semantic Versioning](https://semver.org/).
+
+
diff --git a/charts/dot-ai-stack/docs/architecture/operate.md b/charts/dot-ai-stack/docs/architecture/operate.md
new file mode 100644
index 0000000..b3adef7
--- /dev/null
+++ b/charts/dot-ai-stack/docs/architecture/operate.md
@@ -0,0 +1,460 @@
+# Operate Feature Architecture
+
+This document provides a detailed architecture overview of the Operate feature in the DevOps AI Toolkit.
+
+## Overview
+
+The Operate feature provides AI-powered Day 2 operations for Kubernetes applications. It handles updates, scaling, enhancements, rollbacks, and deletions through natural language intents while applying organizational patterns and policies, validating changes via dry-run, and executing approved operations safely.
+
+## High-Level Architecture
+
+```mermaid
+flowchart TB
+ subgraph Users["User / AI Agent"]
+ Agent["Claude Code, Cursor,
VS Code, etc."]
+ end
+
+ subgraph MCP["MCP Server (dot-ai)"]
+ Operate["operate Tool"]
+ AI["AI Provider"]
+ Session["Session
Manager"]
+ Kubectl["Kubectl Tools"]
+ VectorClient["Vector DB
Client"]
+ end
+
+ subgraph External["External Services"]
+ LLM["Claude, OpenAI,
Gemini, etc."]
+ Qdrant["Qdrant
(Patterns, Policies,
Capabilities)"]
+ end
+
+ subgraph K8s["Kubernetes Cluster"]
+ API["Kubernetes API"]
+ Controller["Controller
(dot-ai-controller)"]
+ Resources["Cluster Resources
Deployments, StatefulSets,
CRDs, etc."]
+ end
+
+ subgraph WebUI["Web UI (dot-ai-ui)"]
+ Viz["Visualization Dashboard
- Current State
- Proposed Changes
- Risk Assessment"]
+ end
+
+ Agent <-->|MCP Protocol| Operate
+ Operate --> AI
+ Operate --> Session
+ Operate --> Kubectl
+ Operate --> VectorClient
+ AI --> LLM
+ AI <-->|Tool Loop| Kubectl
+ VectorClient --> Qdrant
+ Kubectl --> API
+ Operate -->|Execute Commands| API
+ Controller -->|Scan & Embed| Qdrant
+ Controller -->|Watch| Resources
+ Agent -.->|User opens
Visualization URL| WebUI
+```
+
+## Operation Workflow
+
+The operate tool implements a three-phase workflow with persistent session management:
+
+```mermaid
+flowchart TD
+ subgraph Phase1["Phase 1: Intent Analysis"]
+ Intent["User Intent
(natural language)"]
+ CreateSession["Create Session
(opr-{ts}-{uuid})"]
+ EmbedContext["Embed Context:
- Search Patterns
- Search Policies
- Search Capabilities"]
+ ContextCheck{"Capabilities
Found?"}
+ NoCapError["Error: Run
capability scan first"]
+ Investigation["AI Investigation Loop
(max 30 iterations)"]
+ KubectlTools["kubectl Tools:
get, describe, logs,
patch_dryrun, apply_dryrun,
delete_dryrun, get_crd_schema"]
+
+ Intent --> CreateSession --> EmbedContext
+ EmbedContext --> ContextCheck
+ ContextCheck -->|No| NoCapError
+ ContextCheck -->|Yes| Investigation
+ Investigation <-->|Tool Calls| KubectlTools
+ end
+
+ subgraph Phase2["Phase 2: Analysis & Validation"]
+ ParseResponse["Parse AI Response"]
+ DryRunCheck{"Dry-Run
Validated?"}
+ RetryAnalysis["AI Iterates with
Corrected Commands"]
+ Analysis["Generate Analysis:
- Current State
- Proposed Changes
- Commands
- Risk Assessment"]
+ SaveSession["Save Session
(status: analysis_complete)"]
+ ReturnAnalysis["Return with
Visualization URL"]
+
+ Investigation --> ParseResponse --> DryRunCheck
+ DryRunCheck -->|No| RetryAnalysis --> Investigation
+ DryRunCheck -->|Yes| Analysis
+ Analysis --> SaveSession --> ReturnAnalysis
+ end
+
+ subgraph Phase3["Phase 3: User Approval & Execution"]
+ UserChoice["User Approval
(executeChoice=1)"]
+ LoadSession["Load Session"]
+ ExecuteCommands["Execute Commands
(continue-on-error)"]
+ PostValidation{"Has
validationIntent?"}
+ CallRemediate["Call remediate
Internally"]
+ SaveResults["Save Results
(status: executed_*)"]
+ ReturnResults["Return Execution
Results"]
+
+ ReturnAnalysis -.->|"User reviews
and approves"| UserChoice
+ UserChoice --> LoadSession --> ExecuteCommands
+ ExecuteCommands --> PostValidation
+ PostValidation -->|Yes| CallRemediate --> SaveResults
+ PostValidation -->|No| SaveResults
+ SaveResults --> ReturnResults
+ end
+```
+
+## Component Details
+
+### MCP Server (dot-ai)
+
+The MCP server provides the core operations engine:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| `operate` tool | `src/tools/operate.ts` | Entry point, routing, context embedding, formatting |
+| Analysis workflow | `src/tools/operate-analysis.ts` | Intent analysis, AI tool loop, response parsing |
+| Execution workflow | `src/tools/operate-execution.ts` | Command execution, post-validation, results |
+| System Prompt | `prompts/operate-system.md` | AI instructions for operation behavior |
+| User Prompt | `prompts/operate-user.md` | Handlebars template with context injection |
+| `GenericSessionManager` | `src/core/generic-session-manager.ts` | File-based session persistence |
+| `AIProvider` | `src/core/ai-provider.interface.ts` | AI abstraction with tool loop support |
+| `kubectl-tools` | `src/core/kubectl-tools.ts` | Kubectl investigation and validation tools |
+| Vector Services | `src/services/*-vector-service.ts` | Pattern, policy, capability search |
+| `visualization` | `src/core/visualization.ts` | URL generation for web UI |
+
+### Kubectl Investigation & Validation Tools
+
+Tools available during AI analysis:
+
+| Tool | Description |
+|------|-------------|
+| `kubectl_api_resources` | Discover available resources in cluster |
+| `kubectl_get` | List resources with table format |
+| `kubectl_describe` | Detailed resource information with events |
+| `kubectl_logs` | Container logs for debugging |
+| `kubectl_patch_dryrun` | Validate patch operations before execution |
+| `kubectl_apply_dryrun` | Validate apply operations before execution |
+| `kubectl_delete_dryrun` | Validate delete operations before execution |
+| `kubectl_get_crd_schema` | Get CRD schema for custom resources |
+
+### Controller (dot-ai-controller)
+
+The Kubernetes controller provides capability scanning:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| Capability Scanner | `internal/controller/capability_scanner.go` | Discovers cluster resources and capabilities |
+| Embedding Service | `internal/controller/embedding_service.go` | Generates embeddings for semantic search |
+| Qdrant Client | `internal/controller/qdrant_client.go` | Stores capabilities in vector database |
+
+### Web UI (dot-ai-ui)
+
+Provides visualization for operation analysis and execution:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| Visualization Page | `src/pages/Visualization.tsx` | Main page for `/v/{sessionId}` |
+| MermaidRenderer | `src/components/renderers/MermaidRenderer.tsx` | Interactive flowcharts |
+| CardRenderer | `src/components/renderers/CardRenderer.tsx` | Current state and proposed changes |
+| CodeRenderer | `src/components/renderers/CodeRenderer.tsx` | Commands with syntax highlighting |
+| InsightsPanel | `src/components/InsightsPanel.tsx` | AI observations and risk assessment |
+
+## Integration Points
+
+```mermaid
+flowchart LR
+ subgraph MCP["MCP Server"]
+ Operate["operate tool"]
+ AIProvider["AI Provider"]
+ KubectlTools["Kubectl Tools"]
+ SessionMgr["Session Manager"]
+ VectorSvc["Vector Services"]
+ end
+
+ subgraph AI["AI Providers"]
+ Anthropic["Claude API"]
+ OpenAI["OpenAI API"]
+ Google["Gemini API"]
+ Others["xAI, Bedrock,
OpenRouter, etc."]
+ end
+
+ subgraph VectorDB["Vector Database"]
+ Patterns["Patterns
Collection"]
+ Policies["Policies
Collection"]
+ Capabilities["Capabilities
Collection"]
+ end
+
+ subgraph K8s["Kubernetes"]
+ API["API Server"]
+ Controller["Capability
Scanner"]
+ end
+
+ subgraph Storage["Session Storage"]
+ Files["File System
~/.dot-ai/sessions/opr-sessions/"]
+ end
+
+ subgraph UI["Web UI"]
+ Viz["Visualization
Dashboard"]
+ end
+
+ AIProvider <-->|Tool Loop| KubectlTools
+ AIProvider --> Anthropic
+ AIProvider --> OpenAI
+ AIProvider --> Google
+ AIProvider --> Others
+
+ VectorSvc --> Patterns
+ VectorSvc --> Policies
+ VectorSvc --> Capabilities
+
+ KubectlTools -->|Investigation| API
+ Operate -->|Execute Commands| API
+ Controller -->|Scan & Embed| Capabilities
+
+ SessionMgr --> Files
+ Operate -.->|Session URL| Viz
+```
+
+### MCP Server β AI Provider
+
+- **Tool Loop**: AI iteratively calls kubectl tools (max 30 iterations)
+- **Investigation**: Gathers current cluster state to understand resources
+- **Dry-Run Validation**: Validates all commands before proposing
+- **Analysis**: Generates JSON response with changes, commands, and risk assessment
+
+### MCP Server β Vector Database
+
+- **Patterns**: Organizational patterns for operational best practices
+- **Policies**: Policy intents for validation and compliance
+- **Capabilities**: Cluster resource capabilities for intelligent recommendations
+- Capabilities are **mandatory**; patterns/policies are optional
+
+### MCP Server β Kubernetes API
+
+- **Read Operations**: `kubectl get`, `describe`, `logs`
+- **Validation**: `kubectl patch/apply/delete --dry-run=server`
+- **Execution**: Sequential command execution via `child_process.exec()`
+
+### MCP Server β Remediate Tool
+
+- **Post-Execution Validation**: Internally calls remediate with `validationIntent`
+- **Verification**: Confirms operations completed successfully
+- **Error Detection**: Identifies issues introduced by operations
+
+### MCP Server β Web UI
+
+- **Session Storage**: Operation data stored with session IDs
+- **Visualization API**: `/api/v1/visualize/{sessionId}` endpoint
+- **URL Generation**: `WEB_UI_BASE_URL/v/{sessionId}`
+
+## Session Management
+
+Sessions persist workflow state across tool calls:
+
+```
+Session ID Format: opr-{timestamp}-{uuid8}
+Example: opr-1704067200000-a1b2c3d4
+
+Session Data:
+βββ toolName: 'operate'
+βββ intent: "Update my-api to v2.0 with zero downtime"
+βββ context:
+β βββ patterns: OrganizationalPattern[]
+β βββ policies: PolicyIntent[]
+β βββ capabilities: ResourceCapability[]
+βββ proposedChanges:
+β βββ create: ResourceChange[]
+β βββ update: ResourceChange[]
+β βββ delete: ResourceChange[]
+βββ commands: ["kubectl set image...", "kubectl patch..."]
+βββ dryRunValidation:
+β βββ status: 'success' | 'failed'
+β βββ details: string
+βββ patternsApplied: ["Zero-Downtime Rolling Update"]
+βββ capabilitiesUsed: ["metrics-server", "KEDA"]
+βββ policiesChecked: ["Production Update Policy"]
+βββ risks: { level: 'low', description: "..." }
+βββ validationIntent: "Verify deployment rollout complete"
+βββ status: 'analyzing' | 'analysis_complete' | 'executing' | 'executed_*'
+βββ executionResults: [{command, success, output, error}]
+```
+
+### Session States
+
+| State | Description |
+|-------|-------------|
+| `analyzing` | AI is gathering data and generating commands |
+| `analysis_complete` | Analysis done, awaiting user approval |
+| `executing` | Commands are being executed |
+| `executed_successfully` | All commands succeeded |
+| `executed_with_errors` | Some commands failed |
+| `failed` | Analysis or execution failed |
+
+## Organizational Context Integration
+
+The operate tool integrates organizational knowledge via vector database search:
+
+```mermaid
+flowchart TB
+ subgraph Input["User Intent"]
+ Intent["Update my-api to v2.0
with zero downtime"]
+ end
+
+ subgraph Search["Vector DB Search"]
+ PatternSearch["Search Patterns
(limit: 5)"]
+ PolicySearch["Search Policies
(limit: 5)"]
+ CapSearch["Search Capabilities
(limit: 50)"]
+ end
+
+ subgraph Results["Search Results"]
+ Patterns["Patterns Found:
- Zero-Downtime Rolling Update
- Blue-Green Deployment"]
+ Policies["Policies Found:
- Production Update Policy
- Change Management"]
+ Caps["Capabilities Found:
- metrics-server
- KEDA Operator
- Argo Rollouts"]
+ end
+
+ subgraph Prompt["AI Prompt"]
+ Context["Embedded Context:
patterns + policies + capabilities"]
+ end
+
+ Intent --> PatternSearch & PolicySearch & CapSearch
+ PatternSearch --> Patterns
+ PolicySearch --> Policies
+ CapSearch --> Caps
+ Patterns & Policies & Caps --> Context
+```
+
+### Context Priority
+
+1. **Capabilities** (Mandatory): What the cluster can do
+2. **Patterns** (Optional): Organizational best practices
+3. **Policies** (Optional): Compliance and validation rules
+
+## Output Formats
+
+The operate tool returns structured output at different stages:
+
+### Analysis Response
+
+| Field | Description |
+|-------|-------------|
+| `status` | `awaiting_user_approval` |
+| `sessionId` | Session ID for continuation |
+| `visualizationUrl` | URL to view analysis in web UI |
+| `currentState` | Current cluster resource state |
+| `proposedChanges` | Create, update, delete operations |
+| `commands` | Pre-validated kubectl commands |
+| `dryRunValidation` | Dry-run validation results |
+| `patternsApplied` | Applied organizational patterns |
+| `capabilitiesUsed` | Used cluster capabilities |
+| `policiesChecked` | Checked policies |
+| `risks` | Risk assessment (level + description) |
+| `validationIntent` | Post-execution validation instructions |
+
+### Execution Response
+
+| Field | Description |
+|-------|-------------|
+| `status` | `success` or `failed` |
+| `sessionId` | Session ID for reference |
+| `results` | Per-command execution results |
+| `validation` | Post-execution validation summary |
+| `message` | Human-readable summary |
+
+## Error Handling
+
+The operation workflow includes robust error handling:
+
+1. **No Capabilities Found**: Clear guidance to run capability scan first
+2. **Session Not Found**: Guidance to start new operation
+3. **Dry-Run Failures**: AI iterates to fix commands before proposing
+4. **Command Execution Failures**: Continue-on-error, capture all results
+5. **Validation Failures**: Report issues via remediate tool integration
+6. **AI Service Errors**: Logged with request IDs for debugging
+7. **Investigation Timeouts**: Max 30 iterations prevents infinite loops
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `AI_PROVIDER` | AI provider selection | `anthropic` |
+| `ANTHROPIC_API_KEY` | Anthropic API key | Required if using |
+| `OPENAI_API_KEY` | OpenAI API key | Required if using |
+| `QDRANT_URL` | Qdrant vector database URL | `http://localhost:6333` |
+| `QDRANT_API_KEY` | Qdrant API key | Optional |
+| `QDRANT_CAPABILITIES_COLLECTION` | Capabilities collection name | `capabilities` |
+| `KUBECONFIG` | Kubernetes config path | Auto-detected |
+| `DOT_AI_SESSION_DIR` | Session storage directory | `~/.dot-ai/sessions` |
+| `WEB_UI_BASE_URL` | Web UI base URL | Optional |
+| `DEBUG_DOT_AI` | Enable debug logging | `false` |
+
+### Supported AI Providers
+
+| Provider | Models | Notes |
+|----------|--------|-------|
+| Anthropic | Claude Sonnet 4.5, Opus, Haiku | Default, 1M token context |
+| OpenAI | GPT-5.1-codex | |
+| Google | Gemini 3 Pro, Flash | |
+| xAI | Grok-4 | |
+| Amazon Bedrock | Various | Uses AWS credential chain |
+| OpenRouter | Multi-model | Proxy to multiple providers |
+| Custom | Ollama, vLLM, LocalAI | Via `baseURL` config |
+
+## Workflow Example
+
+```
+User Intent: "Update my-api deployment in prod to v2.0 with zero downtime"
+
+1. CONTEXT EMBEDDING
+ ββ embedContext(intent)
+ ββ Search patterns β "Zero-Downtime Rolling Update"
+ ββ Search policies β "Production Update Requirements"
+ ββ Search capabilities β "metrics-server", "KEDA Operator"
+
+2. AI INVESTIGATION LOOP
+ ββ AI Tool Loop (30 iterations max)
+ ββ kubectl_get deployment/my-api -n prod
+ ββ kubectl_describe deployment/my-api -n prod
+ ββ kubectl_patch_dryrun (test maxUnavailable: 0)
+ ββ kubectl_set_image (test v2.0 image --dry-run=server)
+
+3. ANALYSIS GENERATION
+ ββ Session created: opr-1704067200000-a1b2c3d4
+ ββ Status: analysis_complete
+ ββ Current: 3 replicas, my-api:v1.5, maxUnavailable: 1
+ ββ Proposed: image v2.0, maxUnavailable: 0
+ ββ Commands: set image + patch strategy
+ ββ Risk: LOW
+ ββ Visualization URL: https://dot-ai-ui/v/opr-1704067200000-a1b2c3d4
+
+4. USER APPROVAL
+ ββ User reviews analysis in terminal or web UI
+ ββ Calls: operate({ sessionId: 'opr-...', executeChoice: 1 })
+
+5. COMMAND EXECUTION
+ ββ executeOperations()
+ ββ Load session (status: analysis_complete)
+ ββ Update status to executing
+ ββ Execute commands sequentially
+ β ββ kubectl set image deployment/my-api my-api=my-api:v2.0 -n prod
+ β ββ kubectl patch deployment/my-api -n prod -p '{"spec":...}'
+ ββ Call remediate internally for validation
+ ββ Update status to executed_successfully
+
+6. RETURN RESULTS
+ ββ Results: 2 commands succeeded
+ ββ Validation: "Rollout complete, all pods running v2.0"
+ ββ Status: success
+```
+
+## See Also
+
+- [MCP Operate Guide](https://devopstoolkit.ai/mcp/operate/)
+- [Remediate Architecture](./remediate.md)
+- [Recommendation Architecture](./recommendation.md)
+- [Controller Documentation](https://devopstoolkit.ai/controller/)
+- [Web UI Documentation](https://devopstoolkit.ai/ui/)
diff --git a/charts/dot-ai-stack/docs/architecture/query.md b/charts/dot-ai-stack/docs/architecture/query.md
new file mode 100644
index 0000000..c303e63
--- /dev/null
+++ b/charts/dot-ai-stack/docs/architecture/query.md
@@ -0,0 +1,422 @@
+# Query Feature Architecture
+
+This document provides a detailed architecture overview of the Query feature in the DevOps AI Toolkit.
+
+## Overview
+
+The Query feature provides a natural language interface for Kubernetes cluster intelligence. Users can ask questions about cluster resources, capabilities, and status in plain English, with the AI autonomously deciding which tools to call to gather the necessary information.
+
+## High-Level Architecture
+
+```mermaid
+flowchart TB
+ subgraph Users["User / AI Agent"]
+ Agent["Claude Code, Cursor,
VS Code, etc."]
+ end
+
+ subgraph MCP["MCP Server (dot-ai)"]
+ Query["query Tool"]
+ AI["AI Provider"]
+ Session["Session
Manager"]
+ CapTools["Capability
Tools"]
+ ResTools["Resource
Tools"]
+ KubectlTools["Kubectl
Tools"]
+ end
+
+ subgraph External["External Services"]
+ LLM["Claude, OpenAI,
Gemini, etc."]
+ Qdrant["Qdrant
Vector DB"]
+ Embeddings["Embedding
Service"]
+ end
+
+ subgraph K8s["Kubernetes Cluster"]
+ API["Kubernetes API"]
+ Controller["Controller
(dot-ai-controller)"]
+ Resources["Cluster Resources
Pods, Deployments,
Services, etc."]
+ CRDs["Custom Resource
Definitions"]
+ end
+
+ subgraph WebUI["Web UI (dot-ai-ui)"]
+ Viz["Visualization Dashboard
- Mermaid Diagrams
- Tables & Cards
- Code Blocks"]
+ end
+
+ Agent <-->|MCP Protocol| Query
+ Query --> AI
+ Query --> Session
+ AI --> LLM
+ AI <-->|Tool Loop| CapTools
+ AI <-->|Tool Loop| ResTools
+ AI <-->|Tool Loop| KubectlTools
+ CapTools --> Qdrant
+ ResTools --> Qdrant
+ KubectlTools --> API
+ Qdrant --> Embeddings
+ Controller -->|Sync Resources| Qdrant
+ Controller -->|Watch| Resources
+ Controller -->|Watch| CRDs
+ Agent -.->|User opens
Visualization URL| WebUI
+```
+
+## Query Workflow
+
+The query tool operates as an agentic loop where the AI autonomously decides which tools to call:
+
+```mermaid
+flowchart TD
+ subgraph Phase1["Phase 1: Input Processing"]
+ Intent["User Intent
(natural language)"]
+ Validate["Validate Input
(1-1000 chars)"]
+ RequestId["Generate RequestId
for tracking"]
+
+ Intent --> Validate --> RequestId
+ end
+
+ subgraph Phase2["Phase 2: AI Initialization"]
+ LoadPrompt["Load System Prompt
(query-system.md)"]
+ InitProvider["Initialize AI Provider
(from environment)"]
+ BuildTools["Build Tool Definitions
- search_capabilities
- query_capabilities
- search_resources
- query_resources
- kubectl_*"]
+
+ RequestId --> LoadPrompt --> InitProvider --> BuildTools
+ end
+
+ subgraph Phase3["Phase 3: Agentic Tool Loop"]
+ ToolLoop["AI Tool Loop
(Vercel AI SDK)"]
+ Decision{"AI
Decision"}
+ SemanticSearch["Semantic Search
(Qdrant vectors)"]
+ FilterQuery["Filter Query
(Qdrant filters)"]
+ KubectlExec["Kubectl Execution
(live cluster)"]
+ CollectResults["Collect Tool Results"]
+
+ BuildTools --> ToolLoop --> Decision
+ Decision -->|search_* tools| SemanticSearch --> CollectResults
+ Decision -->|query_* tools| FilterQuery --> CollectResults
+ Decision -->|kubectl_* tools| KubectlExec --> CollectResults
+ CollectResults -->|Continue| ToolLoop
+ end
+
+ subgraph Phase4["Phase 4: Response Generation"]
+ MaxIterations{"Max Iterations
Reached?"}
+ FinalResponse["AI Final Response
(JSON format)"]
+ ParseJSON["Parse Summary
from JSON"]
+
+ Decision -->|Done| FinalResponse
+ CollectResults --> MaxIterations
+ MaxIterations -->|Yes| FinalResponse
+ MaxIterations -->|No| ToolLoop
+ FinalResponse --> ParseJSON
+ end
+
+ subgraph Phase5["Phase 5: Session & Output"]
+ CreateSession["Create Session
(qry-{ts}-{uuid})"]
+ StoreData["Store Session Data
- intent
- summary
- toolsUsed
- toolCallsExecuted"]
+ GenURL{"WEB_UI_BASE_URL
Configured?"}
+ VizURL["Generate Visualization URL
{baseUrl}/v/{sessionId}"]
+ BuildOutput["Build QueryOutput
- success: true
- summary
- toolsUsed
- sessionId
- visualizationUrl"]
+
+ ParseJSON --> CreateSession --> StoreData --> GenURL
+ GenURL -->|Yes| VizURL --> BuildOutput
+ GenURL -->|No| BuildOutput
+ end
+```
+
+## Component Details
+
+### MCP Server (dot-ai)
+
+The MCP server hosts the query tool and orchestrates AI-driven investigation:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| `query` tool | `src/tools/query.ts` | Entry point, orchestrates tool loop and session |
+| System Prompt | `prompts/query-system.md` | AI instructions for query behavior |
+| `GenericSessionManager` | `src/core/generic-session-manager.ts` | File-based session persistence |
+| `AIProvider` | `src/core/ai-provider.interface.ts` | AI abstraction with tool loop support |
+| `AIProviderFactory` | `src/core/ai-provider-factory.ts` | Multi-provider factory |
+| `capability-tools` | `src/core/capability-tools.ts` | Semantic search for capabilities |
+| `resource-tools` | `src/core/resource-tools.ts` | Semantic search for resources |
+| `kubectl-tools` | `src/core/kubectl-tools.ts` | Kubectl read-only tools |
+| `CapabilityVectorService` | `src/core/capability-vector-service.ts` | Capability embeddings storage |
+| `ResourceVectorService` | `src/core/resource-vector-service.ts` | Resource embeddings storage |
+| `EmbeddingService` | `src/core/embedding-service.ts` | Multi-provider embedding generation |
+| `visualization` | `src/core/visualization.ts` | URL generation for web UI |
+
+### Query Tools Available to AI
+
+The AI can autonomously call these tools during investigation:
+
+| Tool | Type | Description |
+|------|------|-------------|
+| `search_capabilities` | Semantic | Vector similarity search for resource capabilities |
+| `query_capabilities` | Filter | Structured filter queries for capabilities |
+| `search_resources` | Semantic | Vector similarity search for cluster resources |
+| `query_resources` | Filter | Structured filter queries for resources |
+| `kubectl_api_resources` | Kubectl | List all available API resources |
+| `kubectl_get` | Kubectl | Get resources with current state |
+| `kubectl_describe` | Kubectl | Detailed resource information |
+| `kubectl_logs` | Kubectl | Container logs from pods |
+| `kubectl_events` | Kubectl | Kubernetes events for troubleshooting |
+| `kubectl_get_crd_schema` | Kubectl | OpenAPI v3 schema for CRDs |
+
+### Controller (dot-ai-controller)
+
+The Kubernetes controller syncs cluster data to Qdrant for semantic search:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| `ResourceSyncReconciler` | `internal/controller/resourcesync_controller.go` | Watches all resources, syncs to MCP |
+| `CapabilityScanReconciler` | `internal/controller/capabilityscan_controller.go` | Triggers capability scans on CRD changes |
+| `ResourceSyncConfig` CRD | `api/v1alpha1/resourcesyncconfig_types.go` | Configuration for resource sync |
+| `CapabilityScanConfig` CRD | `api/v1alpha1/capabilityscanconfig_types.go` | Configuration for capability scanning |
+| MCP Resource Client | `internal/controller/resourcesync_mcp.go` | HTTP client for `/api/v1/resources/sync` |
+| MCP Capability Client | `internal/controller/capabilityscan_mcp.go` | HTTP client for `/api/v1/tools/manageOrgData` |
+| Debounce Buffer | `internal/controller/resourcesync_debounce.go` | Batches changes before sync |
+
+### Web UI (dot-ai-ui)
+
+Provides visualization for query results:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| Visualization Page | `src/pages/Visualization.tsx` | Main page for `/v/{sessionId}` |
+| MermaidRenderer | `src/components/renderers/MermaidRenderer.tsx` | Interactive diagrams with zoom/pan |
+| CardRenderer | `src/components/renderers/CardRenderer.tsx` | Resource cards in grid layout |
+| TableRenderer | `src/components/renderers/TableRenderer.tsx` | Tabular data display |
+| CodeRenderer | `src/components/renderers/CodeRenderer.tsx` | Syntax-highlighted code/YAML |
+| InsightsPanel | `src/components/InsightsPanel.tsx` | AI observations display |
+| TabContainer | `src/components/TabContainer.tsx` | Multi-visualization tabs |
+| API Client | `src/api/client.ts` | Data fetching from MCP server |
+
+## Integration Points
+
+```mermaid
+flowchart LR
+ subgraph MCP["MCP Server"]
+ Query["query tool"]
+ AIProvider["AI Provider"]
+ CapTools["Capability Tools"]
+ ResTools["Resource Tools"]
+ KubectlTools["Kubectl Tools"]
+ SessionMgr["Session Manager"]
+ end
+
+ subgraph Vector["Vector Database"]
+ Qdrant["Qdrant"]
+ CapColl["capabilities
collection"]
+ ResColl["resources
collection"]
+ end
+
+ subgraph AI["AI Providers"]
+ Anthropic["Claude API"]
+ OpenAI["OpenAI API"]
+ Google["Gemini API"]
+ Others["xAI, Bedrock,
OpenRouter, etc."]
+ end
+
+ subgraph Embed["Embedding Providers"]
+ OpenAIEmbed["OpenAI
text-embedding-3-small"]
+ GoogleEmbed["Google
gemini-embedding-001"]
+ BedrockEmbed["Bedrock
titan-embed-text-v2"]
+ end
+
+ subgraph K8s["Kubernetes"]
+ API["API Server"]
+ Controller["dot-ai-controller"]
+ end
+
+ subgraph Storage["Session Storage"]
+ Files["File System
~/.dot-ai/sessions/qry-*"]
+ end
+
+ subgraph UI["Web UI"]
+ Viz["Visualization
Dashboard"]
+ end
+
+ AIProvider <-->|Tool Loop| CapTools
+ AIProvider <-->|Tool Loop| ResTools
+ AIProvider <-->|Tool Loop| KubectlTools
+ AIProvider --> Anthropic
+ AIProvider --> OpenAI
+ AIProvider --> Google
+ AIProvider --> Others
+
+ CapTools --> Qdrant
+ ResTools --> Qdrant
+ Qdrant --> CapColl
+ Qdrant --> ResColl
+ Qdrant --> OpenAIEmbed
+ Qdrant --> GoogleEmbed
+ Qdrant --> BedrockEmbed
+
+ KubectlTools --> API
+
+ Controller -->|POST /api/v1/resources/sync| MCP
+ Controller -->|POST /api/v1/tools/manageOrgData| MCP
+ Controller -->|Watch| K8s
+
+ SessionMgr --> Files
+
+ Query -.->|Session URL| Viz
+```
+
+### MCP Server β AI Provider
+
+- **Tool Loop**: AI iteratively calls tools (max 20 iterations by default)
+- **Autonomous Decision**: AI decides which tools to call based on user intent
+- **JSON Output**: AI returns structured JSON with summary
+
+### MCP Server β Qdrant
+
+- **Semantic Search**: Vector similarity using embeddings
+- **Filter Queries**: Structured queries by namespace, kind, labels, etc.
+- **Capability Data**: Resource kinds with capabilities, providers, abstractions
+- **Resource Data**: Cluster resources with labels, annotations, timestamps
+
+### Controller β MCP Server
+
+- **Resource Sync**: Controller watches all resources and syncs to MCP
+- **Capability Scanning**: Controller triggers scans when CRDs change
+- **Debounced Batching**: Changes batched every 10s to reduce API calls
+- **Periodic Resync**: Full resync every 60 minutes for consistency
+
+### MCP Server β Web UI
+
+- **Session Storage**: Query results stored with session IDs
+- **Visualization API**: `/api/v1/visualize/{sessionId}` endpoint
+- **URL Generation**: `WEB_UI_BASE_URL/v/{sessionId}`
+- **Cached Visualizations**: AI-generated visualizations cached in session
+
+## Session Management
+
+Sessions persist query data for visualization:
+
+```
+Session ID Format: qry-{timestamp}-{uuid8}
+Example: qry-1767465086590-a1b2c3d4
+
+Session Data:
+βββ toolName: 'query'
+βββ intent: "What databases are running in the cluster?"
+βββ summary: "Found 3 PostgreSQL clusters and 2 Redis instances..."
+βββ toolsUsed: ['search_capabilities', 'kubectl_get', ...]
+βββ iterations: 5
+βββ toolCallsExecuted:
+β βββ {tool: 'search_capabilities', input: {...}, output: {...}}
+β βββ {tool: 'kubectl_get', input: {...}, output: {...}}
+β βββ ...
+βββ cachedVisualization:
+β βββ title: "Database Resources"
+β βββ visualizations: [...]
+β βββ insights: [...]
+β βββ generatedAt: ISO timestamp
+βββ timestamp: ISO date
+```
+
+## Data Flow: Resource Sync
+
+```mermaid
+flowchart LR
+ subgraph K8s["Kubernetes Cluster"]
+ Resources["Resources
(Pods, Deployments, etc.)"]
+ CRDs["Custom Resources"]
+ end
+
+ subgraph Controller["dot-ai-controller"]
+ Informers["Dynamic
Informers"]
+ CRDWatcher["CRD
Watcher"]
+ DebounceRes["Resource
Debounce Buffer"]
+ DebounceCap["Capability
Debounce Buffer"]
+ end
+
+ subgraph MCP["MCP Server"]
+ SyncAPI["POST /api/v1/
resources/sync"]
+ ManageAPI["POST /api/v1/
tools/manageOrgData"]
+ VectorSvc["Vector
Services"]
+ end
+
+ subgraph Qdrant["Qdrant"]
+ ResColl["resources
collection"]
+ CapColl["capabilities
collection"]
+ end
+
+ Resources -->|Watch| Informers
+ CRDs -->|Watch| CRDWatcher
+ Informers -->|Queue| DebounceRes
+ CRDWatcher -->|Queue| DebounceCap
+ DebounceRes -->|Batch every 10s| SyncAPI
+ DebounceCap -->|Batch every 10s| ManageAPI
+ SyncAPI --> VectorSvc
+ ManageAPI --> VectorSvc
+ VectorSvc -->|Upsert/Delete| ResColl
+ VectorSvc -->|Scan/Delete| CapColl
+```
+
+## Output Formats
+
+The query tool returns structured output:
+
+| Field | Description |
+|-------|-------------|
+| `success` | Boolean indicating query success |
+| `summary` | Human-readable summary of findings |
+| `toolsUsed` | List of tools called during investigation |
+| `iterations` | Number of AI tool loop iterations |
+| `sessionId` | Session ID for visualization |
+| `visualizationUrl` | URL to view results (if configured) |
+| `guidance` | Instructions for agent on presenting results |
+| `error` | Error object with code and message (if failed) |
+
+## Error Handling
+
+The query workflow includes robust error handling:
+
+1. **Input Validation**: Intent must be non-empty string (1-1000 chars)
+2. **AI Provider Errors**: Logged with request IDs for debugging
+3. **Vector DB Unavailable**: Falls back to keyword search or kubectl-only
+4. **JSON Parsing Failures**: Original AI response logged for analysis
+5. **Tool Execution Errors**: Captured in toolCallsExecuted, investigation continues
+6. **Max Iterations**: Default 20 iterations prevents infinite loops
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `AI_PROVIDER` | AI provider selection | `anthropic` |
+| `ANTHROPIC_API_KEY` | Anthropic API key | Required if using |
+| `OPENAI_API_KEY` | OpenAI API key | Required if using |
+| `QDRANT_URL` | Qdrant vector DB endpoint | `http://localhost:6333` |
+| `QDRANT_API_KEY` | Qdrant authentication | Optional |
+| `QDRANT_CAPABILITIES_COLLECTION` | Capabilities collection name | `capabilities` |
+| `QDRANT_RESOURCES_COLLECTION` | Resources collection name | `resources` |
+| `CUSTOM_EMBEDDINGS_API_KEY` | Embedding service API key | Falls back to provider key |
+| `KUBECONFIG` | Kubernetes config path | Auto-detected |
+| `WEB_UI_BASE_URL` | Web UI base URL | Optional |
+| `DEBUG_DOT_AI` | Enable debug logging | `false` |
+
+### Supported AI Providers
+
+| Provider | Models | Notes |
+|----------|--------|-------|
+| Anthropic | Claude Sonnet 4.5, Opus, Haiku | Default, 1M token context |
+| OpenAI | GPT-5.1-codex | |
+| Google | Gemini 3 Pro, Flash | |
+| xAI | Grok-4 | |
+| Amazon Bedrock | Various | Uses AWS credential chain |
+| OpenRouter | Multi-model | Proxy to multiple providers |
+| Custom | Ollama, vLLM, LocalAI | Via `baseURL` config |
+
+### Supported Embedding Providers
+
+| Provider | Model | Dimensions |
+|----------|-------|------------|
+| OpenAI | text-embedding-3-small | 1536 |
+| Google | gemini-embedding-001 | 768 |
+| Amazon Bedrock | titan-embed-text-v2:0 | 1024 |
+
+## See Also
+
+- [MCP Query Guide](https://devopstoolkit.ai/mcp/query/)
+- [Controller Documentation](https://devopstoolkit.ai/controller/)
+- [Web UI Documentation](https://devopstoolkit.ai/ui/)
+- [Vector Database Setup](https://devopstoolkit.ai/setup/qdrant/)
diff --git a/charts/dot-ai-stack/docs/architecture/recommendation.md b/charts/dot-ai-stack/docs/architecture/recommendation.md
new file mode 100644
index 0000000..5302008
--- /dev/null
+++ b/charts/dot-ai-stack/docs/architecture/recommendation.md
@@ -0,0 +1,325 @@
+# Recommendation Feature Architecture
+
+This document provides a detailed architecture overview of the Recommendation feature in the DevOps AI Toolkit.
+
+## Overview
+
+The Recommendation feature provides AI-powered Kubernetes deployment recommendations. It analyzes user intent, discovers cluster capabilities, and generates deployment solutions with full manifest generation and deployment capabilities.
+
+## High-Level Architecture
+
+```mermaid
+flowchart TB
+ subgraph Users["User / AI Agent"]
+ Agent["Claude Code, Cursor,
VS Code, etc."]
+ end
+
+ subgraph MCP["MCP Server (dot-ai)"]
+ Recommend["recommend Tool"]
+ AI["AI Provider"]
+ Vector["Vector DB
Client"]
+ Discovery["Discovery
Engine"]
+ end
+
+ subgraph External["External Services"]
+ LLM["Claude, OpenAI,
Ollama, etc."]
+ Qdrant["Qdrant
(Semantic Search)"]
+ end
+
+ subgraph K8s["Kubernetes Cluster"]
+ API["Kubernetes API"]
+ Controller["Controller
(dot-ai-controller)"]
+ Resources["Deployed Resources
Deployments, Services,
Ingress, HPA, PDB, etc."]
+ end
+
+ subgraph WebUI["Web UI (dot-ai-ui)"]
+ Viz["Visualization Dashboard
- Solution Comparisons
- Resource Diagrams
- Generated Manifests"]
+ end
+
+ Agent <-->|MCP Protocol| Recommend
+ Recommend --> AI
+ Recommend --> Vector
+ Recommend --> Discovery
+ AI --> LLM
+ Vector -->|Embeddings| LLM
+ Vector --> Qdrant
+ Discovery --> API
+ Recommend --> API
+ Controller --> Resources
+ Controller -->|Sync with Embeddings| Qdrant
+ Agent -.->|User opens
Visualization URL| WebUI
+```
+
+## Recommendation Workflow Stages
+
+The recommendation tool operates as a unified multi-stage workflow:
+
+```mermaid
+flowchart TD
+ subgraph Stage1["Stage 1: recommend"]
+ UserIntent["User Intent"]
+ IntentCheck{"Intent < 100 chars?"}
+ Refine["Return Refinement Guidance"]
+ CapSearch["Capability Search
(Vector DB)"]
+ AIRank["AI Ranking
(Claude)"]
+ CapMatch{"Capability
Match?"}
+ ResourceSol["Generate Resource-Based
Solutions"]
+ HelmSearch["Search ArtifactHub
for Helm Charts"]
+ Solutions["Solutions with solutionIds
+ visualization URL"]
+
+ UserIntent --> IntentCheck
+ IntentCheck -->|Yes| Refine
+ IntentCheck -->|No| CapSearch
+ CapSearch --> AIRank
+ AIRank --> CapMatch
+ CapMatch -->|Yes| ResourceSol
+ CapMatch -->|No| HelmSearch
+ ResourceSol --> Solutions
+ HelmSearch --> Solutions
+ end
+
+ subgraph Stage2["Stage 2: chooseSolution"]
+ SelectSol["solutionId"]
+ LoadSession["Load Session"]
+ GenQuestions["Generate Questions
(if Helm)"]
+ ReturnQuestions["Required Questions"]
+
+ SelectSol --> LoadSession --> GenQuestions --> ReturnQuestions
+ end
+
+ subgraph Stage3["Stage 3-6: answerQuestion"]
+ Required["answerQuestion:required
(name, namespace, image, port)"]
+ Basic["answerQuestion:basic
(replicas, resources, ingress)"]
+ Advanced["answerQuestion:advanced
(probes, PDB, security)"]
+ Open["answerQuestion:open
(free-form, AI enhancement)"]
+ Ready["ready_for_manifest_generation"]
+
+ Required --> Basic --> Advanced --> Open --> Ready
+ end
+
+ subgraph Stage4["Stage 7: generateManifests"]
+ GenType{"Solution
Type?"}
+
+ subgraph Capability["Capability-Based"]
+ C1["1. Retrieve Schemas"]
+ C2["2. AI Generation"]
+ C3["3. YAML Validation"]
+ C4["4. kubectl Dry-Run"]
+ C5["5. Retry Loop (max 10)"]
+ C6["6. Package Output"]
+ C1 --> C2 --> C3 --> C4 --> C5 --> C6
+ end
+
+ subgraph Helm["Helm-Based"]
+ H1["1. Fetch Chart"]
+ H2["2. AI Values Gen"]
+ H3["3. Helm Dry-Run"]
+ H4["4. Retry Loop"]
+ H1 --> H2 --> H3 --> H4
+ end
+
+ GenType -->|Capability| C1
+ GenType -->|Helm| H1
+ C6 --> Manifests
+ H4 --> Manifests
+ Manifests["Manifests + visualization URL"]
+ end
+
+ subgraph Stage5["Stage 8: deployManifests"]
+ DeployType{"Solution
Type?"}
+
+ CapDeploy["kubectl apply -k
--wait"]
+ HelmDeploy["helm upgrade
--install --wait"]
+ Status["Deployment Status
+ Next Steps"]
+
+ DeployType -->|Capability| CapDeploy
+ DeployType -->|Helm| HelmDeploy
+ CapDeploy --> Status
+ HelmDeploy --> Status
+ end
+
+ Solutions --> SelectSol
+ ReturnQuestions --> Required
+ Ready --> GenType
+ Manifests --> DeployType
+```
+
+## Component Details
+
+### MCP Server (dot-ai)
+
+The MCP server is the core recommendation engine:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| `recommend` tool | `src/tools/recommend.ts` | Entry point, routes to stages, generates solutions |
+| `chooseSolution` | `src/tools/choose-solution.ts` | Loads selected solution, returns questions |
+| `answerQuestion` | `src/tools/answer-question.ts` | Processes answers, manages stage progression |
+| `generateManifests` | `src/tools/generate-manifests.ts` | AI manifest generation with validation loop |
+| `deployManifests` | `src/tools/deploy-manifests.ts` | Deploys via kubectl or helm |
+| `ResourceRecommender` | `src/core/schema.ts` | AI-powered solution ranking and filtering |
+| `CapabilityVectorService` | `src/core/capability-vector-service.ts` | Semantic search for capabilities |
+| `PatternVectorService` | `src/core/pattern-vector-service.ts` | Organizational pattern matching |
+| `PolicyVectorService` | `src/core/policy-vector-service.ts` | Policy enforcement |
+| `GenericSessionManager` | `src/core/generic-session-manager.ts` | Session state management |
+| `ArtifactHubService` | `src/core/artifacthub.ts` | Helm chart discovery |
+
+### Controller (dot-ai-controller)
+
+The Kubernetes controller manages deployed solutions:
+
+| CRD | Description |
+|-----|-------------|
+| `Solution` | Groups related resources, manages ownerReferences, aggregates health |
+| `ResourceSyncConfig` | Syncs resource metadata to MCP for semantic search |
+| `CapabilityScanConfig` | Scans cluster for resource capabilities |
+| `RemediationPolicy` | Event-driven remediation (separate feature) |
+
+### Web UI (dot-ai-ui)
+
+Provides visualization for recommendation results:
+
+- **Visualization Page** (`/v/{sessionId}`) - Renders solution comparisons
+- **Mermaid Diagrams** - Architecture and flow diagrams
+- **Resource Cards** - Solution component details
+- **Code Blocks** - Generated manifests with syntax highlighting
+- **Tables** - Configuration summaries
+
+## Integration Points
+
+```mermaid
+flowchart LR
+ subgraph MCP["MCP Server"]
+ Recommend["recommend tool"]
+ Schema["ResourceRecommender"]
+ CapVec["CapabilityVectorService"]
+ PatVec["PatternVectorService"]
+ PolVec["PolicyVectorService"]
+ Discovery["Discovery Engine"]
+ ArtHub["ArtifactHubService"]
+ end
+
+ subgraph VectorDB["Qdrant"]
+ Capabilities["Capabilities
Collection"]
+ Patterns["Patterns
Collection"]
+ Policies["Policies
Collection"]
+ end
+
+ subgraph AI["AI Provider"]
+ Claude["Claude API"]
+ OpenAI["OpenAI API"]
+ end
+
+ subgraph K8s["Kubernetes"]
+ API["API Server"]
+ Controller["dot-ai-controller"]
+ end
+
+ subgraph External["External"]
+ ArtifactHub["ArtifactHub API"]
+ end
+
+ subgraph UI["Web UI"]
+ Viz["Visualization
Dashboard"]
+ end
+
+ CapVec <-->|Semantic Search| Capabilities
+ PatVec <-->|Pattern Match| Patterns
+ PolVec <-->|Policy Lookup| Policies
+
+ Schema -->|Solution Ranking| Claude
+ Schema -->|Solution Ranking| OpenAI
+
+ Discovery -->|kubectl explain| API
+ Recommend -->|kubectl apply| API
+
+ ArtHub -->|Chart Search| ArtifactHub
+
+ Recommend -.->|Session URL| Viz
+ Controller -->|Watch Resources| API
+```
+
+### MCP Server β Vector DB (Qdrant)
+
+- **Capability Storage**: Resource capabilities with semantic embeddings
+- **Pattern Storage**: Organizational patterns for solution enhancement
+- **Policy Storage**: Policy intents for configuration enforcement
+- **Semantic Search**: Natural language queries matched to stored data
+
+### MCP Server β Kubernetes API
+
+- **Resource Discovery**: `kubectl api-resources`, `kubectl explain`
+- **Schema Retrieval**: OpenAPI schemas for manifest generation
+- **Manifest Validation**: `kubectl apply --dry-run=server`
+- **Deployment**: `kubectl apply`, `helm upgrade --install`
+
+### MCP Server β AI Provider
+
+- **Solution Assembly**: Ranking and filtering discovered capabilities
+- **Question Generation**: Creating configuration questions from schemas
+- **Manifest Generation**: Generating YAML from solution + answers
+- **Helm Values**: Generating values.yaml for chart installations
+
+### MCP Server β Web UI
+
+- **Session Storage**: Solution data stored with session IDs
+- **Visualization API**: `/api/visualize/{sessionId}` endpoint
+- **URL Generation**: `WEB_UI_BASE_URL/v/{sessionId}`
+
+### Controller β MCP Server
+
+- **Resource Sync**: Controller syncs resource metadata to MCP
+- **Capability Scan**: Controller triggers capability discovery
+- **Solution CR**: MCP generates Solution CR for controller management
+
+## Session Management
+
+Sessions persist workflow state across tool calls:
+
+```
+Session ID Format: sol-{timestamp}-{uuid8}
+Example: sol-1765409923079-fa3f055c
+
+Session Data:
+βββ toolName: 'recommend'
+βββ stage: 'recommend' | 'generateManifests' | ...
+βββ intent: "Deploy PostgreSQL database"
+βββ type: 'single' | 'combination' | 'helm'
+βββ score: 96
+βββ description: "Multi-cloud PostgreSQL via DevOps Toolkit"
+βββ resources: [{kind, apiVersion, group, description}]
+βββ chart: {repository, chartName, version} (if Helm)
+βββ questions: {required, basic, advanced, open}
+βββ answers: {questionId: value}
+βββ appliedPatterns: ["DevOps Toolkit DB Pattern"]
+βββ generatedManifests: {type, files, helmCommand}
+βββ timestamp: ISO date
+```
+
+## Output Formats
+
+The recommendation tool supports three output formats for capability-based solutions:
+
+| Format | Description | Files Generated |
+|--------|-------------|-----------------|
+| `raw` | Plain YAML manifests | `manifests.yaml` |
+| `helm` | Helm chart structure | `Chart.yaml`, `values.yaml`, `templates/*.yaml` |
+| `kustomize` | Kustomize overlay | `kustomization.yaml`, `base/`, `overlays/` |
+
+## Error Handling
+
+The recommendation workflow includes robust error handling:
+
+1. **Intent Refinement**: Vague intents get guidance, not failure
+2. **Validation Loops**: Up to 10 retries for manifest generation
+3. **Capability Gaps**: Clear error when enhancement isn't possible
+4. **Session Expiry**: Graceful handling of expired sessions
+5. **AI Service Errors**: Fallback to original solution on enhancement failure
+
+## See Also
+
+- [MCP Recommendation Guide](https://devopstoolkit.ai/mcp/recommend/)
+- [Capability Management Guide](https://devopstoolkit.ai/mcp/capability-management/)
+- [Pattern Management Guide](https://devopstoolkit.ai/mcp/pattern-management/)
+- [Controller Documentation](https://devopstoolkit.ai/controller/)
+- [Web UI Documentation](https://devopstoolkit.ai/ui/)
diff --git a/charts/dot-ai-stack/docs/architecture/remediate.md b/charts/dot-ai-stack/docs/architecture/remediate.md
new file mode 100644
index 0000000..b387656
--- /dev/null
+++ b/charts/dot-ai-stack/docs/architecture/remediate.md
@@ -0,0 +1,435 @@
+# Remediate Feature Architecture
+
+This document provides a detailed architecture overview of the Remediate feature in the DevOps AI Toolkit.
+
+## Overview
+
+The Remediate feature provides AI-powered Kubernetes issue analysis and remediation. It investigates problems using kubectl tools, identifies root causes with confidence scoring, and executes verified fixes with optional post-execution validation.
+
+## High-Level Architecture
+
+```mermaid
+flowchart TB
+ subgraph Users["User / AI Agent"]
+ Agent["Claude Code, Cursor,
VS Code, etc."]
+ end
+
+ subgraph MCP["MCP Server (dot-ai)"]
+ Remediate["remediate Tool"]
+ AI["AI Provider"]
+ Session["Session
Manager"]
+ Kubectl["Kubectl Tools"]
+ end
+
+ subgraph External["External Services"]
+ LLM["Claude, OpenAI,
Gemini, etc."]
+ end
+
+ subgraph K8s["Kubernetes Cluster"]
+ API["Kubernetes API"]
+ Controller["Controller
(dot-ai-controller)"]
+ Events["Kubernetes Events"]
+ Resources["Cluster Resources
Pods, Deployments,
Services, etc."]
+ end
+
+ subgraph WebUI["Web UI (dot-ai-ui)"]
+ Viz["Visualization Dashboard
- Investigation Flow
- Root Cause Analysis
- Remediation Commands"]
+ end
+
+ subgraph Notifications["Notifications"]
+ Slack["Slack"]
+ GChat["Google Chat"]
+ end
+
+ Agent <-->|MCP Protocol| Remediate
+ Remediate --> AI
+ Remediate --> Session
+ Remediate --> Kubectl
+ AI --> LLM
+ AI <-->|Tool Loop| Kubectl
+ Kubectl --> API
+ Remediate -->|Execute Commands| API
+ Controller -->|Watch| Events
+ Controller -->|RemediationPolicy| Remediate
+ Controller -.->|Webhook| Slack
+ Controller -.->|Webhook| GChat
+ Events --> Resources
+ Agent -.->|User opens
Visualization URL| WebUI
+```
+
+## Remediation Workflow
+
+The remediate tool operates as a multi-phase workflow with persistent session management:
+
+```mermaid
+flowchart TD
+ subgraph Phase1["Phase 1: Investigation"]
+ Issue["Issue Description"]
+ CreateSession["Create Session
(rem-{ts}-{uuid})"]
+ Investigation["AI Investigation Loop
(max 30 iterations)"]
+ KubectlTools["kubectl Tools:
get, describe, logs,
events, api-resources, patch"]
+
+ Issue --> CreateSession --> Investigation
+ Investigation <-->|Tool Calls| KubectlTools
+ end
+
+ subgraph Phase2["Phase 2: Analysis"]
+ ParseResponse["Parse AI Response"]
+ StatusCheck{"Issue
Status?"}
+ AlreadyResolved["Return: Issue already
resolved/non-existent"]
+ Analysis["Root Cause Analysis
+ Confidence Score
+ Contributing Factors"]
+
+ Investigation --> ParseResponse --> StatusCheck
+ StatusCheck -->|resolved| AlreadyResolved
+ StatusCheck -->|non_existent| AlreadyResolved
+ StatusCheck -->|active| Analysis
+ end
+
+ subgraph Phase3["Phase 3: Execution Decision"]
+ ModeCheck{"Execution
Mode?"}
+
+ subgraph Manual["Manual Mode (default)"]
+ ReturnChoices["Return 2 Choices:
1. Execute via MCP
2. Execute via Agent"]
+ WaitApproval["await_user_approval"]
+ end
+
+ subgraph Auto["Automatic Mode"]
+ ThresholdCheck{"Confidence >= threshold
AND Risk <= maxRisk?"}
+ AutoExecute["Execute Automatically"]
+ Fallback["Return with
fallbackReason"]
+ end
+
+ Analysis --> ModeCheck
+ ModeCheck -->|manual| ReturnChoices --> WaitApproval
+ ModeCheck -->|automatic| ThresholdCheck
+ ThresholdCheck -->|Yes| AutoExecute
+ ThresholdCheck -->|No| Fallback
+ end
+
+ subgraph Phase4["Phase 4: Command Execution"]
+ UserChoice["User Choice
(executeChoice=1 or 2)"]
+ Choice1{"Choice?"}
+ ExecuteMCP["Execute via MCP
(child_process.exec)"]
+ ExecuteAgent["Return Commands
for Agent Execution"]
+ LogResults["Log Results
(success/failure/output)"]
+
+ WaitApproval --> UserChoice --> Choice1
+ Choice1 -->|1| ExecuteMCP --> LogResults
+ Choice1 -->|2| ExecuteAgent
+ AutoExecute --> ExecuteMCP
+ end
+
+ subgraph Phase5["Phase 5: Validation"]
+ ValidationCheck{"All Commands
Succeeded?"}
+ HasValidation{"Has validationIntent?"}
+ RecursiveCall["Recursive Investigation
with validationIntent"]
+ WaitReconcile["Wait 30s
(automatic mode)"]
+ FinalStatus["Final Status:
resolved / still_active"]
+
+ LogResults --> ValidationCheck
+ ValidationCheck -->|Yes| HasValidation
+ ValidationCheck -->|No| FinalStatus
+ HasValidation -->|Yes| WaitReconcile --> RecursiveCall --> FinalStatus
+ HasValidation -->|No| FinalStatus
+ end
+```
+
+## Component Details
+
+### MCP Server (dot-ai)
+
+The MCP server is the core remediation engine:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| `remediate` tool | `src/tools/remediate.ts` | Entry point, orchestrates investigation and execution |
+| System Prompt | `prompts/remediate-system.md` | AI instructions for investigation behavior |
+| `GenericSessionManager` | `src/core/generic-session-manager.ts` | File-based session persistence |
+| `AIProvider` | `src/core/ai-provider.interface.ts` | AI abstraction with tool loop support |
+| `AIProviderFactory` | `src/core/ai-provider-factory.ts` | Multi-provider factory (Anthropic, OpenAI, etc.) |
+| `kubectl-tools` | `src/core/kubectl-tools.ts` | Kubectl investigation tools |
+| `visualization` | `src/core/visualization.ts` | URL generation for web UI |
+
+### Kubectl Investigation Tools
+
+Tools available during AI investigation:
+
+| Tool | Description |
+|------|-------------|
+| `kubectl_api_resources` | Discover available resources in cluster |
+| `kubectl_get` | List resources with table format (compact) |
+| `kubectl_describe` | Detailed resource information with events |
+| `kubectl_logs` | Container logs (supports `--previous` for crashes) |
+| `kubectl_events` | Cluster events for understanding state changes |
+| `kubectl_patch_dryrun` | Validate patches before actual execution |
+
+### Controller (dot-ai-controller)
+
+The Kubernetes controller provides event-driven remediation:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| `RemediationPolicy` CRD | `config/crd/bases/` | Custom resource for remediation rules |
+| Policy Controller | `internal/controller/remediationpolicy_controller.go` | Event matching and MCP dispatch |
+| Rate Limiter | `internal/controller/remediationpolicy_ratelimit.go` | Per-object cooldowns and rate limits |
+| MCP Client | `internal/controller/remediationpolicy_mcp.go` | HTTP client for remediate tool |
+| Cooldown State | ConfigMaps | Persistent cooldown state across restarts |
+
+### Web UI (dot-ai-ui)
+
+Provides visualization for remediation results:
+
+| Component | File | Description |
+|-----------|------|-------------|
+| Visualization Page | `src/pages/Visualization.tsx` | Main page for `/v/{sessionId}` |
+| MermaidRenderer | `src/components/renderers/MermaidRenderer.tsx` | Interactive flowcharts (collapsible) |
+| CardRenderer | `src/components/renderers/CardRenderer.tsx` | Issue/solution cards |
+| CodeRenderer | `src/components/renderers/CodeRenderer.tsx` | Commands and logs with syntax highlighting |
+| InsightsPanel | `src/components/InsightsPanel.tsx` | AI observations display |
+| API Client | `src/api/client.ts` | Data fetching from MCP server |
+
+## Integration Points
+
+```mermaid
+flowchart LR
+ subgraph MCP["MCP Server"]
+ Remediate["remediate tool"]
+ AIProvider["AI Provider"]
+ KubectlTools["Kubectl Tools"]
+ SessionMgr["Session Manager"]
+ end
+
+ subgraph AI["AI Providers"]
+ Anthropic["Claude API"]
+ OpenAI["OpenAI API"]
+ Google["Gemini API"]
+ Others["xAI, Bedrock,
OpenRouter, etc."]
+ end
+
+ subgraph K8s["Kubernetes"]
+ API["API Server"]
+ Controller["RemediationPolicy
Controller"]
+ Events["Kubernetes Events"]
+ end
+
+ subgraph Storage["Session Storage"]
+ Files["File System
tmp/sessions/*.json"]
+ ConfigMaps["ConfigMaps
(cooldown state)"]
+ end
+
+ subgraph UI["Web UI"]
+ Viz["Visualization
Dashboard"]
+ end
+
+ subgraph Notif["Notifications"]
+ Slack["Slack"]
+ GChat["Google Chat"]
+ end
+
+ AIProvider <-->|Tool Loop| KubectlTools
+ AIProvider --> Anthropic
+ AIProvider --> OpenAI
+ AIProvider --> Google
+ AIProvider --> Others
+
+ KubectlTools -->|Investigation| API
+ Remediate -->|Execute Commands| API
+
+ Controller -->|Watch| Events
+ Controller -->|HTTP POST| Remediate
+ Controller -.->|Webhook| Slack
+ Controller -.->|Webhook| GChat
+
+ SessionMgr --> Files
+ Controller --> ConfigMaps
+
+ Remediate -.->|Session URL| Viz
+```
+
+### MCP Server β AI Provider
+
+- **Tool Loop**: AI iteratively calls kubectl tools (max 30 iterations)
+- **Investigation**: Gathers cluster data to understand the issue
+- **Analysis**: Parses JSON response with root cause, confidence, and remediation steps
+- **Validation**: Optional recursive investigation after command execution
+
+### MCP Server β Kubernetes API
+
+- **Read Operations**: `kubectl get`, `describe`, `logs`, `events`
+- **Validation**: `kubectl patch --dry-run=server`
+- **Execution**: `child_process.exec()` for remediation commands
+
+### Controller β MCP Server
+
+- **Event-Driven**: Controller watches Kubernetes events
+- **Policy Matching**: Events matched against RemediationPolicy selectors
+- **HTTP Dispatch**: POST to MCP `/api/v1/tools/remediate`
+- **Rate Limiting**: Per-object cooldowns prevent remediation storms
+
+### MCP Server β Web UI
+
+- **Session Storage**: Remediation data stored with session IDs
+- **Visualization API**: `/api/v1/visualize/{sessionId}` endpoint
+- **URL Generation**: `WEB_UI_BASE_URL/v/{sessionId}`
+
+### Controller β Notifications
+
+- **Slack Webhooks**: Controller sends remediation events to Slack channels
+- **Google Chat Webhooks**: Controller sends remediation events to Google Chat spaces
+- **Secret References**: Webhook URLs stored securely in Kubernetes Secrets
+- **Event Types**: Notifications sent on remediation start, success, and failure
+
+## Session Management
+
+Sessions persist workflow state across tool calls:
+
+```
+Session ID Format: rem-{timestamp}-{uuid8}
+Example: rem-1767465086590-11029192
+
+Session Data:
+βββ toolName: 'remediate'
+βββ issue: "Pod my-app is crashing with OOMKilled"
+βββ mode: 'manual' | 'automatic'
+βββ interaction_id: (for evaluation dataset)
+βββ status: 'investigating' | 'analysis_complete' | 'executed_*' | ...
+βββ finalAnalysis:
+β βββ rootCause: "Container memory limit too low"
+β βββ confidence: 0.92
+β βββ factors: ["High memory usage", "No HPA"]
+β βββ remediation:
+β β βββ summary: "Increase memory limit"
+β β βββ actions: [{description, command, risk, rationale}]
+β β βββ risk: 'low' | 'medium' | 'high'
+β βββ validationIntent: "Verify pod is running"
+βββ executionResults: [{command, success, output, error}]
+βββ timestamp: ISO date
+```
+
+### Session States
+
+| State | Description |
+|-------|-------------|
+| `investigating` | AI is gathering data via kubectl tools |
+| `analysis_complete` | Analysis done, awaiting user approval |
+| `failed` | Investigation failed (error) |
+| `executed_successfully` | All commands succeeded |
+| `executed_with_errors` | Some commands failed |
+| `cancelled` | User cancelled the remediation |
+
+## RemediationPolicy CRD
+
+The controller uses a CRD for event-driven remediation:
+
+```yaml
+apiVersion: dot-ai.devopstoolkit.live/v1alpha1
+kind: RemediationPolicy
+metadata:
+ name: oom-killer-policy
+spec:
+ eventSelectors:
+ - type: Warning
+ reason: OOMKilled
+ involvedObjectKind: Pod
+ namespace: production
+ message: ".*memory.*" # Regex support
+ mode: automatic # Override per selector
+ confidenceThreshold: 0.9
+ maxRiskLevel: low
+
+ mcpEndpoint: https://mcp.example.com/api/v1/tools
+ mcpAuthSecretRef:
+ name: mcp-auth
+ key: token
+ mcpTool: remediate
+
+ mode: manual # Default mode
+ confidenceThreshold: 0.8
+ maxRiskLevel: low
+
+ rateLimiting:
+ eventsPerMinute: 10
+ cooldownMinutes: 5
+
+ notifications:
+ slack:
+ webhookSecretRef:
+ name: slack-webhook
+ key: url
+ channel: "#alerts"
+ googleChat:
+ webhookSecretRef:
+ name: gchat-webhook
+ key: url
+
+status:
+ totalEventsProcessed: 150
+ successfulRemediations: 142
+ failedRemediations: 8
+ rateLimitedEvents: 25
+ lastProcessedEvent: "2025-01-07T10:30:00Z"
+```
+
+## Output Formats
+
+The remediate tool returns structured output:
+
+| Field | Description |
+|-------|-------------|
+| `status` | `success`, `failed`, or `awaiting_user_approval` |
+| `sessionId` | Session ID for continuation or visualization |
+| `investigation.iterations` | Number of AI tool loop iterations |
+| `investigation.dataGathered` | List of kubectl tools called |
+| `analysis.rootCause` | Identified root cause |
+| `analysis.confidence` | Confidence score (0-1) |
+| `analysis.factors` | Contributing factors |
+| `remediation.summary` | Human-readable summary |
+| `remediation.actions` | Commands with risk levels |
+| `remediation.risk` | Overall risk level |
+| `validationIntent` | Post-execution validation instructions |
+| `executionChoices` | Available execution options |
+| `results` | Execution results (if executed) |
+
+## Error Handling
+
+The remediation workflow includes robust error handling:
+
+1. **Session Not Found**: Clear guidance to start new investigation
+2. **AI Service Errors**: Logged with request IDs for debugging
+3. **JSON Parsing Failures**: Original AI response logged for analysis
+4. **Command Execution Failures**: Individual command results tracked
+5. **Validation Failures**: Recursive investigation continues despite errors
+6. **Investigation Timeouts**: Max 30 iterations prevents infinite loops
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `AI_PROVIDER` | AI provider selection | `anthropic` |
+| `ANTHROPIC_API_KEY` | Anthropic API key | Required if using |
+| `OPENAI_API_KEY` | OpenAI API key | Required if using |
+| `KUBECONFIG` | Kubernetes config path | Auto-detected |
+| `DOT_AI_SESSION_DIR` | Session storage directory | `./tmp/sessions` |
+| `WEB_UI_BASE_URL` | Web UI base URL | Optional |
+| `DEBUG_DOT_AI` | Enable debug logging | `false` |
+
+### Supported AI Providers
+
+| Provider | Models | Notes |
+|----------|--------|-------|
+| Anthropic | Claude Sonnet 4.5, Opus, Haiku | Default, 1M token context |
+| OpenAI | GPT-5.1-codex | |
+| Google | Gemini 3 Pro, Flash | |
+| xAI | Grok-4 | |
+| Amazon Bedrock | Various | Uses AWS credential chain |
+| OpenRouter | Multi-model | Proxy to multiple providers |
+| Custom | Ollama, vLLM, LocalAI | Via `baseURL` config |
+
+## See Also
+
+- [MCP Remediate Guide](https://devopstoolkit.ai/mcp/remediate/)
+- [Controller Documentation](https://devopstoolkit.ai/controller/)
+- [Web UI Documentation](https://devopstoolkit.ai/ui/)
+- [RemediationPolicy Reference](https://devopstoolkit.ai/controller/remediationpolicy/)
diff --git a/charts/dot-ai-stack/docs/index.md b/charts/dot-ai-stack/docs/index.md
new file mode 100644
index 0000000..3acd920
--- /dev/null
+++ b/charts/dot-ai-stack/docs/index.md
@@ -0,0 +1,277 @@
+# DevOps AI Toolkit Stack
+
+**Deploy the complete DevOps AI Toolkit stack with a single Helm command.**
+
+## Overview
+
+The dot-ai-stack umbrella chart installs all DevOps AI Toolkit components with a single command:
+- **DevOps AI Toolkit** - MCP server for AI-powered Kubernetes operations
+- **DevOps AI Toolkit Controller** - Kubernetes controller for intelligent resource management and autonomous operations
+- **DevOps AI Toolkit Web UI** - Web interface for visual cluster management
+
+> **Note:** This guide covers Kubernetes deployment using the umbrella Helm chart. For other installation options (Docker, NPX, individual charts, etc.), see [devopstoolkit.ai](https://devopstoolkit.ai).
+
+## Prerequisites
+
+- **Helm 3.x** installed
+- **kubectl** configured with cluster access
+- **AI API keys** for AI-powered features (see [AI Model Configuration](https://devopstoolkit.ai/docs/ai-engine/setup/deployment#ai-model-configuration) for supported providers)
+
+## Step 1: Create a Local Cluster (Optional)
+
+> Skip this step if you already have a Kubernetes cluster. You will also need a [Gateway API](https://gateway-api.sigs.k8s.io/implementations/) implementation or an ingress controller installed in the cluster.
+
+Create a Kind cluster with ingress port mappings:
+
+```bash
+kind create cluster --name dot-ai-stack --config - < **Note:** This quickstart uses ingress-nginx for simplicity. The community-maintained ingress-nginx project reached [End of Life in March 2026](https://github.com/kubernetes/ingress-nginx). For production clusters, consider using [Gateway API](https://devopstoolkit.ai/docs/ai-engine/setup/gateway-api) instead β see also the [deployment guide](https://devopstoolkit.ai/docs/ai-engine/setup/deployment) for configuration options.
+
+```bash
+kubectl apply \
+ --filename https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
+```
+
+Wait for the ingress controller to be ready:
+
+```bash
+kubectl wait --namespace ingress-nginx \
+ --for=condition=ready pod \
+ --selector=app.kubernetes.io/component=controller \
+ --timeout=90s
+```
+
+## Step 2: Set Environment Variables
+
+Set your API keys for AI-powered features:
+
+```bash
+export ANTHROPIC_API_KEY="your-anthropic-api-key"
+```
+
+> **Note:** Multiple AI providers are supported. See [AI Model Configuration](https://devopstoolkit.ai/docs/ai-engine/setup/deployment#ai-model-configuration) for all options including Google Gemini, AWS Bedrock, Azure OpenAI, and others.
+
+Generate random authentication tokens for the MCP server and Web UI:
+
+```bash
+export DOT_AI_AUTH_TOKEN=$(openssl rand -base64 32)
+
+export DOT_AI_UI_AUTH_TOKEN=$(openssl rand -base64 32)
+```
+
+## Step 3: Install the Stack
+
+Install the complete dot-ai stack with a single Helm command:
+
+```bash
+helm upgrade --install dot-ai-stack \
+ oci://ghcr.io/vfarcic/dot-ai-stack/charts/dot-ai-stack \
+ --namespace dot-ai --create-namespace \
+ --set dot-ai.secrets.anthropic.apiKey=$ANTHROPIC_API_KEY \
+ --set dot-ai.secrets.auth.token=$DOT_AI_AUTH_TOKEN \
+ --set dot-ai.localEmbeddings.enabled=true \
+ --set dot-ai.ingress.enabled=true \
+ --set dot-ai.ingress.className=nginx \
+ --set dot-ai.ingress.host=dot-ai.127.0.0.1.nip.io \
+ --set dot-ai.webUI.baseUrl=http://dot-ai-ui.127.0.0.1.nip.io \
+ --set dot-ai-ui.uiAuth.token=$DOT_AI_UI_AUTH_TOKEN \
+ --set dot-ai-ui.ingress.enabled=true \
+ --set dot-ai-ui.ingress.host=dot-ai-ui.127.0.0.1.nip.io \
+ --wait
+```
+
+> **Note:** Replace the ingress hosts with your actual domain names for production deployments.
+
+This installs:
+- **dot-ai** - MCP server with ingress at `dot-ai.127.0.0.1.nip.io`
+- **dot-ai-controller** - Kubernetes controller for autonomous operations
+- **dot-ai-ui** - Web interface at `dot-ai-ui.127.0.0.1.nip.io`
+- **Qdrant** - Vector database for pattern and policy storage
+- **Local Embeddings** - In-cluster embedding service ([HuggingFace TEI](https://huggingface.co/docs/text-embeddings-inference)) so semantic search works without external embedding API keys
+- **ResourceSyncConfig** - Enables resource discovery
+- **CapabilityScanConfig** - Enables cluster capability scanning
+
+## Step 4: Verify Installation
+
+Check that all pods are running:
+
+```bash
+kubectl get pods --namespace dot-ai
+```
+
+You should see all pods in `Running` status:
+
+```
+NAME READY STATUS RESTARTS AGE
+dot-ai-577db5b4fc-j8kgf 1/1 Running 0 50s
+dot-ai-controller-manager-c898b5697-dqk2m 1/1 Running 0 50s
+dot-ai-stack-qdrant-0 1/1 Running 0 50s
+dot-ai-ui-69d586db8b-ccqrm 1/1 Running 0 50s
+```
+
+Test the MCP server health:
+
+```bash
+curl -H "Authorization: Bearer $DOT_AI_AUTH_TOKEN" \
+ http://dot-ai.127.0.0.1.nip.io/healthz
+```
+
+Expected output:
+
+```json
+{"status":"ok"}
+```
+
+## Step 5: Choose Your Client
+
+The DevOps AI Toolkit can be accessed through two client options - **MCP** or **CLI**. Both provide AI agent integration with full feature parity.
+
+### Option A: MCP Client
+
+**Best for:** Curated high-level operations designed to minimize context window usage.
+
+Create the MCP client configuration file:
+
+```bash
+cat > .mcp.json << EOF
+{
+ "mcpServers": {
+ "dot-ai": {
+ "type": "http",
+ "url": "http://dot-ai.127.0.0.1.nip.io",
+ "headers": {
+ "Authorization": "Bearer $DOT_AI_AUTH_TOKEN"
+ }
+ }
+ }
+}
+EOF
+```
+
+> **Note:** This example creates `.mcp.json` in the current directory for Claude Code. Other MCP-enabled agents may expect the configuration in a different location (e.g., `~/.config/` or within the agent's settings). Consult your agent's documentation for the correct path.
+
+**Learn more:** [MCP Setup Documentation](https://devopstoolkit.ai/docs/ai-engine/setup/deployment)
+
+### Option B: CLI Client
+
+**Best for:** Comprehensive API access with lower token overhead for AI agents, plus scripting and automation support.
+
+Install the CLI:
+
+**macOS (Homebrew):**
+```bash
+brew install vfarcic/tap/dot-ai
+```
+
+**Windows (Scoop):**
+```powershell
+scoop bucket add dot-ai https://github.com/vfarcic/scoop-dot-ai
+scoop install dot-ai
+```
+
+**Other platforms:** Download from [releases](https://github.com/vfarcic/dot-ai-cli/releases) or see [installation guide](https://devopstoolkit.ai/docs/cli/setup/installation/).
+
+Configure the CLI:
+
+```bash
+export DOT_AI_URL="http://dot-ai.127.0.0.1.nip.io"
+```
+
+Generate skills for your AI agent:
+
+```bash
+# For Claude Code
+dot-ai skills generate --agent claude-code
+
+# For Cursor
+dot-ai skills generate --agent cursor
+
+# For Windsurf
+dot-ai skills generate --agent windsurf
+```
+
+**Learn more:** [CLI Quick Start](https://devopstoolkit.ai/docs/cli/quick-start/) | [Installation](https://devopstoolkit.ai/docs/cli/setup/installation/) | [Agent Integration](https://devopstoolkit.ai/docs/cli/guides/skills-generation/)
+
+### Choosing Between MCP and CLI
+
+- **Use MCP** for simpler, high-level operations with minimal tool descriptions
+- **Use CLI** for comprehensive API access with lower token costs and better economy for agents executing multiple commands
+
+## Step 6: Start Using
+
+Launch your AI agent:
+
+```bash
+claude
+```
+
+> **Note:** If your agent doesn't automatically detect the client, explicitly invoke it with "Use dot-ai MCP" or "Use dot-ai CLI" depending on which client you configured.
+
+Try these example prompts:
+
+| What You Want | Example Prompt |
+|---------------|----------------|
+| Check system status | "Show dot-ai status" |
+| Query cluster | "What pods are running in the dot-ai namespace?" |
+| List capabilities | "List all capabilities" |
+| Deploy an app | "I want to deploy a web application" |
+| Fix issues | "Something is wrong with my database" |
+
+## Configuration
+
+Override any component value by prefixing with the chart name:
+
+```bash
+--set dot-ai.resources.limits.memory=4Gi
+--set dot-ai-controller.resources.limits.memory=1Gi
+--set dot-ai-ui.ingress.host=ui.example.com
+```
+
+For available options, see each component's documentation:
+- [DevOps AI Toolkit values](https://devopstoolkit.ai/docs/ai-engine/setup/deployment)
+- [Controller values](https://devopstoolkit.ai/docs/controller/)
+- [Web UI values](https://devopstoolkit.ai/docs/ui/)
+
+## Next Steps
+
+- [MCP Tools Overview](https://devopstoolkit.ai/docs/ai-engine/tools/overview) - Complete feature reference
+- [Pattern Management](https://devopstoolkit.ai/docs/ai-engine/organizational-data/patterns) - Create organizational patterns
+- [Policy Management](https://devopstoolkit.ai/docs/ai-engine/organizational-data/policies) - Define governance policies
+
+## Cleanup
+
+To remove the stack:
+
+```bash
+helm uninstall dot-ai-stack --namespace dot-ai
+
+kubectl delete namespace dot-ai
+```
+
+To delete the Kind cluster:
+
+```bash
+kind delete cluster --name dot-ai-stack
+```
diff --git a/charts/dot-ai-stack/pyproject.toml b/charts/dot-ai-stack/pyproject.toml
new file mode 100644
index 0000000..90e5895
--- /dev/null
+++ b/charts/dot-ai-stack/pyproject.toml
@@ -0,0 +1,35 @@
+[tool.towncrier]
+directory = "changelog.d"
+filename = "docs/CHANGELOG.md"
+title_format = "## [{version}] - {project_date}"
+issue_format = "[#{issue}](https://github.com/vfarcic/dot-ai-stack/issues/{issue})"
+
+# feature: New user-facing functionality (users directly interact with or benefit from this)
+[[tool.towncrier.type]]
+directory = "feature"
+name = "Features"
+showcontent = true
+
+# bugfix: Bug fixes
+[[tool.towncrier.type]]
+directory = "bugfix"
+name = "Bug Fixes"
+showcontent = true
+
+# breaking: Breaking changes that require user action
+[[tool.towncrier.type]]
+directory = "breaking"
+name = "Breaking Changes"
+showcontent = true
+
+# doc: Documentation improvements only
+[[tool.towncrier.type]]
+directory = "doc"
+name = "Documentation"
+showcontent = true
+
+# misc: Internal improvements, CI/CD, tooling, refactoring (improves the project but users don't directly see it)
+[[tool.towncrier.type]]
+directory = "misc"
+name = "Other Changes"
+showcontent = true
diff --git a/charts/dot-ai-stack/renovate.json b/charts/dot-ai-stack/renovate.json
new file mode 100644
index 0000000..0ba86d5
--- /dev/null
+++ b/charts/dot-ai-stack/renovate.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": ["config:recommended"],
+ "packageRules": [
+ {
+ "matchManagers": ["github-actions"],
+ "automerge": true
+ }
+ ]
+}
diff --git a/charts/dot-ai-stack/templates/capabilityscanconfig.yaml b/charts/dot-ai-stack/templates/capabilityscanconfig.yaml
new file mode 100644
index 0000000..d372f74
--- /dev/null
+++ b/charts/dot-ai-stack/templates/capabilityscanconfig.yaml
@@ -0,0 +1,18 @@
+{{- if .Values.capabilityScan.enabled }}
+apiVersion: dot-ai.devopstoolkit.live/v1alpha1
+kind: CapabilityScanConfig
+metadata:
+ name: default-scan
+ annotations:
+ "helm.sh/hook": post-install,post-upgrade
+ "helm.sh/hook-weight": "10"
+spec:
+ mcp:
+ endpoint: http://dot-ai:3456/api/v1/tools/manageOrgData
+ authSecretRef:
+ name: dot-ai-secrets
+ key: auth-token
+ {{- with .Values.capabilityScan.debounceWindowSeconds }}
+ debounceWindowSeconds: {{ . }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/templates/resourcesyncconfig.yaml b/charts/dot-ai-stack/templates/resourcesyncconfig.yaml
new file mode 100644
index 0000000..d52f28a
--- /dev/null
+++ b/charts/dot-ai-stack/templates/resourcesyncconfig.yaml
@@ -0,0 +1,20 @@
+{{- if .Values.resourceSync.enabled }}
+apiVersion: dot-ai.devopstoolkit.live/v1alpha1
+kind: ResourceSyncConfig
+metadata:
+ name: default-sync
+ annotations:
+ "helm.sh/hook": post-install,post-upgrade
+ "helm.sh/hook-weight": "10"
+spec:
+ mcpEndpoint: http://dot-ai:3456/api/v1/resources/sync
+ mcpAuthSecretRef:
+ name: dot-ai-secrets
+ key: auth-token
+ {{- with .Values.resourceSync.debounceWindowSeconds }}
+ debounceWindowSeconds: {{ . }}
+ {{- end }}
+ {{- with .Values.resourceSync.resyncIntervalMinutes }}
+ resyncIntervalMinutes: {{ . }}
+ {{- end }}
+{{- end }}
diff --git a/charts/dot-ai-stack/values.yaml b/charts/dot-ai-stack/values.yaml
new file mode 100644
index 0000000..c7123a4
--- /dev/null
+++ b/charts/dot-ai-stack/values.yaml
@@ -0,0 +1,69 @@
+# dot-ai-stack umbrella chart values
+# Override sub-chart values by nesting under the chart name
+
+# dot-ai MCP server configuration (core component - always deployed)
+# Docs: https://devopstoolkit.ai/docs/mcp/setup/kubernetes-setup
+dot-ai:
+ # Fixed service name so dot-ai-ui can find it at http://dot-ai:3456
+ fullnameOverride: dot-ai
+ # Enable local embeddings to use in-cluster HuggingFace TEI for semantic search
+ # without requiring external embedding API keys (recommended for new installations)
+ # localEmbeddings:
+ # enabled: true
+ # Example overrides:
+ # secrets:
+ # anthropic:
+ # apiKey: "your-api-key"
+ # openai:
+ # apiKey: "your-api-key"
+ # auth:
+ # token: "your-auth-token"
+ # ingress:
+ # enabled: true
+ # className: nginx
+ # webUI:
+ # baseUrl: "http://dot-ai-ui.example.com" # Should match dot-ai-ui.ingress.host
+
+# dot-ai-controller configuration
+# Docs: https://devopstoolkit.ai/docs/controller/setup-guide
+dot-ai-controller:
+ # Set to false to disable the controller (e.g., for MCP-only usage)
+ enabled: true
+ fullnameOverride: dot-ai-controller
+ # Example overrides:
+ # resources:
+ # limits:
+ # cpu: 1000m
+ # memory: 1Gi
+
+# dot-ai-ui web interface configuration
+# Docs: https://devopstoolkit.ai/docs/ui/setup/kubernetes-setup
+dot-ai-ui:
+ # Set to false to disable the web UI
+ enabled: true
+ fullnameOverride: dot-ai-ui
+ # Example overrides:
+ # dotAi:
+ # auth:
+ # token: "your-auth-token" # Must match dot-ai secrets.auth.token
+ # uiAuth:
+ # token: "your-ui-auth-token" # Token to access the Web UI dashboard
+ # secretRef:
+ # name: "" # Or reference existing secret (recommended for production)
+ # key: "ui-auth-token"
+ # ingress:
+ # enabled: true
+ # host: dot-ai-ui.example.com # Set dot-ai.webUI.baseUrl to enable MCP links to UI
+
+# ResourceSyncConfig - enables resource discovery and semantic search
+# Docs: https://devopstoolkit.ai/docs/controller/resource-sync-guide
+resourceSync:
+ enabled: true
+ # debounceWindowSeconds: 30 # Seconds to wait before syncing after a change (default: 10, max: 300)
+ # resyncIntervalMinutes: 120 # Minutes between full resyncs (default: 60, max: 1440)
+
+# CapabilityScanConfig - enables cluster capability discovery
+# Docs: https://devopstoolkit.ai/docs/controller/capability-scan-guide
+capabilityScan:
+ enabled: true
+ # debounceWindowSeconds: 30 # Seconds to wait before scanning after a change (default: 10, max: 300)
diff --git a/manifests/dot-ai-stack/values.yaml b/manifests/dot-ai-stack/values.yaml
new file mode 100644
index 0000000..84c5a17
--- /dev/null
+++ b/manifests/dot-ai-stack/values.yaml
@@ -0,0 +1,53 @@
+# ===== dot-ai (core) =====
+dot-ai:
+ fullnameOverride: dot-ai
+
+ ai:
+ provider: openai
+ model: qwen2.5:1.5b
+ customEndpoint:
+ enabled: true
+ baseURL: "http://ollama.ai-stack.svc.cluster.local:11434/v1"
+
+ localEmbeddings:
+ enabled: true
+
+ secrets:
+ auth:
+ token: "YAmCMjPflHQAuDqYZufDKKvqWUysrwgfOzXCzwHFEM4="
+
+ ingress:
+ enabled: true
+ className: traefik
+ host: dot-ai.dvirlabs.com
+
+ webUI:
+ baseUrl: "https://dot-ai-ui.dvirlabs.com"
+
+
+# ===== controller =====
+dot-ai-controller:
+ enabled: true
+ fullnameOverride: dot-ai-controller
+
+
+# ===== UI =====
+dot-ai-ui:
+ enabled: true
+ fullnameOverride: dot-ai-ui
+
+ uiAuth:
+ token: "+zjZEF8v9l9zG3evXgTuF9RIdMtpfOXr6u6o4+bXl5Y="
+
+ ingress:
+ enabled: true
+ className: traefik
+ host: dot-ai-ui.dvirlabs.com
+
+
+# ===== features =====
+resourceSync:
+ enabled: true
+
+capabilityScan:
+ enabled: true
\ No newline at end of file