diff --git a/charts/repowise/.helmignore b/charts/repowise/.helmignore new file mode 100644 index 0000000..ea11244 --- /dev/null +++ b/charts/repowise/.helmignore @@ -0,0 +1,13 @@ +.DS_Store +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +*.swp +*.bak +*.tmp +*.orig +*~ diff --git a/charts/repowise/Chart.yaml b/charts/repowise/Chart.yaml new file mode 100644 index 0000000..3d45811 --- /dev/null +++ b/charts/repowise/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: repowise +description: A Helm chart for deploying Repowise — AI-powered codebase documentation and context engine +type: application +version: 0.1.0 +appVersion: "0.1.0" +home: https://github.com/repowise-dev/repowise +sources: + - https://github.com/repowise-dev/repowise +maintainers: + - name: repowise-dev + url: https://github.com/repowise-dev +keywords: + - repowise + - code-documentation + - ai + - mcp + - codebase-context diff --git a/charts/repowise/README.md b/charts/repowise/README.md new file mode 100644 index 0000000..ca59539 --- /dev/null +++ b/charts/repowise/README.md @@ -0,0 +1,142 @@ +# Repowise Helm Chart + +Deploy [Repowise](https://github.com/repowise-dev/repowise) on Kubernetes. + +## Prerequisites + +- Kubernetes 1.24+ +- Helm 3.x +- A container image built from `docker/Dockerfile` pushed to your registry + +## Quick Start + +```bash +# Build and push the image +docker build -t your-registry/repowise:0.1.0 -f docker/Dockerfile . +docker push your-registry/repowise:0.1.0 + +# Install the chart +helm install repowise ./charts/repowise \ + --set image.repository=your-registry/repowise \ + --set image.tag=0.1.0 \ + --set apiKeys.anthropic=sk-ant-... +``` + +## Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount` | Number of replicas (only 1 supported with SQLite) | `1` | +| `image.repository` | Container image repository | `repowise/repowise` | +| `image.tag` | Image tag (defaults to appVersion) | `""` | +| `repowise.embedder` | Embedder backend (`mock`, `openai`, `gemini`) | `mock` | +| `repowise.dbUrl` | Database connection URL | `sqlite+aiosqlite:////data/wiki.db` | +| `repowise.backendPort` | API server port | `7337` | +| `repowise.frontendPort` | Web UI port | `3000` | +| `apiKeys.anthropic` | Anthropic API key | `""` | +| `apiKeys.openai` | OpenAI API key | `""` | +| `apiKeys.gemini` | Gemini API key | `""` | +| `existingSecret` | Use an existing Secret for API keys | `""` | +| `persistence.enabled` | Enable PVC for `/data` | `true` | +| `persistence.size` | PVC size | `10Gi` | +| `persistence.storageClass` | Storage class (empty = cluster default) | `""` | +| `ingress.enabled` | Enable Ingress | `false` | +| `ingress.className` | Ingress class name | `""` | +| `ingress.hosts` | Ingress host rules | see `values.yaml` | + +## Using an Existing Secret + +If you manage secrets externally (e.g., Sealed Secrets, External Secrets): + +```bash +kubectl create secret generic my-repowise-keys \ + --from-literal=ANTHROPIC_API_KEY=sk-ant-... \ + --from-literal=OPENAI_API_KEY= \ + --from-literal=GEMINI_API_KEY= + +helm install repowise ./charts/repowise \ + --set existingSecret=my-repowise-keys +``` + +## Ingress Example + +```yaml +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: repowise.example.com + paths: + - path: / + pathType: Prefix + servicePort: frontend + tls: + - secretName: repowise-tls + hosts: + - repowise.example.com +``` + +## Auto-Cloning Repositories + +You can declare repos in `values.yaml` and the chart will automatically clone them, register with the API, and trigger indexing via a post-install/upgrade Job. + +### Public repos + +```bash +helm install repowise ./charts/repowise \ + --set repos[0].name=my-app \ + --set repos[0].url=https://github.com/org/my-app.git \ + --set repos[0].branch=main +``` + +### Private repos (GitHub PAT) + +```bash +helm install repowise ./charts/repowise \ + --set repos[0].name=my-private-app \ + --set repos[0].url=https://github.com/org/my-private-app.git \ + --set gitCredentials.github.username=my-user \ + --set gitCredentials.github.token=ghp_... +``` + +### Private repos (existing Secret) + +```bash +kubectl create secret generic git-creds \ + --from-literal=git-credentials='https://my-user:ghp_token@github.com' + +helm install repowise ./charts/repowise \ + --set repos[0].name=my-private-app \ + --set repos[0].url=https://github.com/org/my-private-app.git \ + --set gitCredentials.secretName=git-creds +``` + +### Multiple repos + +```yaml +repos: + - name: frontend + url: https://github.com/org/frontend.git + branch: main + - name: backend + url: https://github.com/org/backend.git + branch: develop + - name: infra + url: https://github.com/org/infra.git +``` + +The Job clones repos to `/data/repos/`, waits for the API to be healthy, registers each repo, and triggers a sync. Monitor progress with: + +```bash +kubectl logs job/repowise-repo-init -n +``` + +## Persistence + +Repowise stores its SQLite database and indexed repository data under `/data`. The chart creates a PVC by default. To disable (data lost on pod restart): + +```bash +helm install repowise ./charts/repowise --set persistence.enabled=false +``` diff --git a/charts/repowise/templates/NOTES.txt b/charts/repowise/templates/NOTES.txt new file mode 100644 index 0000000..dbccc23 --- /dev/null +++ b/charts/repowise/templates/NOTES.txt @@ -0,0 +1,19 @@ +Repowise has been deployed! + +1. Get the application URLs: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT_FRONTEND=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[1].nodePort}" services {{ include "repowise.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "Web UI: http://$NODE_IP:$NODE_PORT_FRONTEND" +{{- else if contains "ClusterIP" .Values.service.type }} + kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "repowise.fullname" . }} 3000:{{ .Values.service.frontendPort }} 7337:{{ .Values.service.backendPort }} + echo "Web UI: http://localhost:3000" + echo "API: http://localhost:7337" +{{- end }} + +2. To use as an MCP server, point your client at the API: + http://{{ include "repowise.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.backendPort }} diff --git a/charts/repowise/templates/_helpers.tpl b/charts/repowise/templates/_helpers.tpl new file mode 100644 index 0000000..885b529 --- /dev/null +++ b/charts/repowise/templates/_helpers.tpl @@ -0,0 +1,71 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "repowise.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "repowise.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 for the chart label. +*/}} +{{- define "repowise.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "repowise.labels" -}} +helm.sh/chart: {{ include "repowise.chart" . }} +{{ include "repowise.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "repowise.selectorLabels" -}} +app.kubernetes.io/name: {{ include "repowise.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use. +*/}} +{{- define "repowise.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "repowise.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Name of the Secret holding API keys. +*/}} +{{- define "repowise.secretName" -}} +{{- if .Values.existingSecret }} +{{- .Values.existingSecret }} +{{- else }} +{{- include "repowise.fullname" . }} +{{- end }} +{{- end }} diff --git a/charts/repowise/templates/deployment.yaml b/charts/repowise/templates/deployment.yaml new file mode 100644 index 0000000..7e36c9f --- /dev/null +++ b/charts/repowise/templates/deployment.yaml @@ -0,0 +1,203 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "repowise.fullname" . }} + labels: + {{- include "repowise.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: Recreate + selector: + matchLabels: + {{- include "repowise.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "repowise.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "repowise.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- if or .Values.repos .Values.postgresql.enabled }} + initContainers: + {{- if .Values.postgresql.enabled }} + - name: wait-for-postgres + image: postgres:16-alpine + command: ["/bin/sh", "-c"] + args: + - | + echo "Waiting for PostgreSQL..." + until pg_isready -h {{ include "repowise.fullname" . }}-postgresql -p 5432 -U {{ .Values.postgresql.auth.username }}; do + sleep 2 + done + echo "PostgreSQL is ready!" + {{- end }} + {{- if .Values.repos }} + - name: clone-repos + image: bitnami/git:latest + command: ["/bin/bash", "-c"] + args: + - | + set -e + mkdir -p /data/repos + # Allow git to work in repos owned by different users (initContainer runs + # as root, main container as uid 1000). Write gitconfig for both users. + git config --global --add safe.directory '*' + mkdir -p /data/.gitconfig-home + printf '[safe]\n\tdirectory = *\n' > /data/.gitconfig-home/.gitconfig + {{- if or .Values.gitCredentials.secretName .Values.gitCredentials.github.token }} + git config --global credential.helper 'store --file /tmp/.git-credentials' + cp /git-credentials/git-credentials /tmp/.git-credentials + chmod 600 /tmp/.git-credentials + {{- end }} + {{- range .Values.repos }} + echo "=== {{ .name }} ===" + if [ ! -d "/data/repos/{{ .name }}/.git" ]; then + git clone --branch {{ .branch | default "main" }} --single-branch {{ .url }} "/data/repos/{{ .name }}" + else + cd "/data/repos/{{ .name }}" + git fetch origin {{ .branch | default "main" }} + git reset --hard origin/{{ .branch | default "main" }} + fi + {{- end }} + echo "Done." + volumeMounts: + - name: data + mountPath: /data + {{- if or .Values.gitCredentials.secretName .Values.gitCredentials.github.token }} + - name: git-credentials + mountPath: /git-credentials + readOnly: true + {{- end }} + {{- end }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: backend + containerPort: {{ .Values.repowise.backendPort }} + protocol: TCP + - name: frontend + containerPort: {{ .Values.repowise.frontendPort }} + protocol: TCP + env: + - name: REPOWISE_DB_URL + {{- if .Values.postgresql.enabled }} + value: "postgresql+asyncpg://{{ .Values.postgresql.auth.username }}:{{ .Values.postgresql.auth.password }}@{{ include "repowise.fullname" . }}-postgresql:5432/{{ .Values.postgresql.auth.database }}" + {{- else }} + value: {{ .Values.repowise.dbUrl | quote }} + {{- end }} + - name: REPOWISE_EMBEDDER + value: {{ .Values.repowise.embedder | quote }} + - name: PORT_BACKEND + value: {{ .Values.repowise.backendPort | quote }} + - name: PORT_FRONTEND + value: {{ .Values.repowise.frontendPort | quote }} + - name: HOSTNAME + value: "0.0.0.0" + {{- if .Values.repos }} + - name: HOME + value: "/data/.gitconfig-home" + {{- end }} + - name: ANTHROPIC_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "repowise.secretName" . }} + key: ANTHROPIC_API_KEY + optional: true + - name: OPENAI_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "repowise.secretName" . }} + key: OPENAI_API_KEY + optional: true + - name: GEMINI_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "repowise.secretName" . }} + key: GEMINI_API_KEY + optional: true + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /data + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.repos }} + - name: register-repos + image: curlimages/curl:latest + command: ["/bin/sh", "-c"] + args: + - | + API="http://localhost:{{ .Values.repowise.backendPort }}" + echo "Waiting for API..." + until curl -sf "$API/health" > /dev/null 2>&1; do sleep 3; done + echo "API ready!" + {{- range .Values.repos }} + echo "--- {{ .name }} ---" + # Register (upsert) + curl -sf -X POST "$API/api/repos" \ + -H "Content-Type: application/json" \ + -d '{"name":"{{ .name }}","local_path":"/data/repos/{{ .name }}","url":"{{ .url }}","default_branch":"{{ .branch | default "main" }}"}' \ + > /dev/null 2>&1 && echo " registered" || echo " (exists)" + # Check if already indexed (has head_commit) + REPO_DATA=$(curl -sf "$API/api/repos" 2>/dev/null || echo "[]") + REPO_ID=$(echo "$REPO_DATA" | sed -n 's/.*"id":"\([^"]*\)","name":"{{ .name }}".*/\1/p') + HEAD=$(echo "$REPO_DATA" | sed -n 's/.*"name":"{{ .name }}".*"head_commit":"\([^"]*\)".*/\1/p') + if [ -n "$REPO_ID" ] && [ -z "$HEAD" ]; then + echo " not yet indexed, triggering sync..." + curl -sf -X POST "$API/api/repos/$REPO_ID/sync" > /dev/null 2>&1 && echo " sync triggered" || echo " (sync already running)" + elif [ -n "$HEAD" ]; then + echo " already indexed (head: $HEAD), skipping sync." + fi + {{- end }} + echo "All repos processed. Sleeping forever." + sleep infinity + {{- end }} + volumes: + - name: data + {{- if .Values.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ include "repowise.fullname" . }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if or .Values.gitCredentials.secretName .Values.gitCredentials.github.token }} + - name: git-credentials + secret: + secretName: {{ .Values.gitCredentials.secretName | default (printf "%s-git-credentials" (include "repowise.fullname" .)) }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/repowise/templates/git-credentials-secret.yaml b/charts/repowise/templates/git-credentials-secret.yaml new file mode 100644 index 0000000..ed15ec9 --- /dev/null +++ b/charts/repowise/templates/git-credentials-secret.yaml @@ -0,0 +1,14 @@ +{{- if and .Values.gitCredentials.github.token (not .Values.gitCredentials.secretName) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "repowise.fullname" . }}-git-credentials + labels: + {{- include "repowise.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "0" +type: Opaque +stringData: + git-credentials: "https://{{ .Values.gitCredentials.github.username }}:{{ .Values.gitCredentials.github.token }}@github.com" +{{- end }} diff --git a/charts/repowise/templates/ingress.yaml b/charts/repowise/templates/ingress.yaml new file mode 100644 index 0000000..7745a03 --- /dev/null +++ b/charts/repowise/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "repowise.fullname" . }} + labels: + {{- include "repowise.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "repowise.fullname" $ }} + port: + name: {{ .servicePort | default "frontend" }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/repowise/templates/postgresql.yaml b/charts/repowise/templates/postgresql.yaml new file mode 100644 index 0000000..841ba4c --- /dev/null +++ b/charts/repowise/templates/postgresql.yaml @@ -0,0 +1,115 @@ +{{- if .Values.postgresql.enabled }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "repowise.fullname" . }}-postgresql + labels: + {{- include "repowise.labels" . | nindent 4 }} +type: Opaque +data: + POSTGRES_USER: {{ .Values.postgresql.auth.username | b64enc | quote }} + POSTGRES_PASSWORD: {{ .Values.postgresql.auth.password | b64enc | quote }} + POSTGRES_DB: {{ .Values.postgresql.auth.database | b64enc | quote }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "repowise.fullname" . }}-postgresql + labels: + {{- include "repowise.labels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + name: postgresql + selector: + {{- include "repowise.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "repowise.fullname" . }}-postgresql + labels: + {{- include "repowise.labels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +spec: + serviceName: {{ include "repowise.fullname" . }}-postgresql + replicas: 1 + selector: + matchLabels: + {{- include "repowise.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: postgresql + template: + metadata: + labels: + {{- include "repowise.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: postgresql + spec: + containers: + - name: postgresql + image: pgvector/pgvector:pg16 + ports: + - containerPort: 5432 + protocol: TCP + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ include "repowise.fullname" . }}-postgresql + key: POSTGRES_USER + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "repowise.fullname" . }}-postgresql + key: POSTGRES_PASSWORD + - name: POSTGRES_DB + valueFrom: + secretKeyRef: + name: {{ include "repowise.fullname" . }}-postgresql + key: POSTGRES_DB + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + - name: POSTGRES_INITDB_ARGS + value: "--auth-host=md5" + args: ["-c", "max_connections=4000", "-c", "shared_buffers=2GB", "-c", "work_mem=16MB"] + volumeMounts: + - name: pgdata + mountPath: /var/lib/postgresql/data + livenessProbe: + exec: + command: ["pg_isready", "-U", {{ .Values.postgresql.auth.username | quote }}] + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + readinessProbe: + exec: + command: ["pg_isready", "-U", {{ .Values.postgresql.auth.username | quote }}] + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + resources: + requests: + cpu: 1000m + memory: 4Gi + limits: + cpu: 4000m + memory: 8Gi + {{- if .Values.postgresql.primary.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: pgdata + spec: + accessModes: ["ReadWriteOnce"] + {{- if .Values.postgresql.primary.persistence.storageClass }} + storageClassName: {{ .Values.postgresql.primary.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgresql.primary.persistence.size }} + {{- end }} +{{- end }} diff --git a/charts/repowise/templates/pvc.yaml b/charts/repowise/templates/pvc.yaml new file mode 100644 index 0000000..869441b --- /dev/null +++ b/charts/repowise/templates/pvc.yaml @@ -0,0 +1,21 @@ +{{- if .Values.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "repowise.fullname" . }} + labels: + {{- include "repowise.labels" . | nindent 4 }} + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} +{{- end }} diff --git a/charts/repowise/templates/secret.yaml b/charts/repowise/templates/secret.yaml new file mode 100644 index 0000000..7c8d1a4 --- /dev/null +++ b/charts/repowise/templates/secret.yaml @@ -0,0 +1,13 @@ +{{- if not .Values.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "repowise.fullname" . }} + labels: + {{- include "repowise.labels" . | nindent 4 }} +type: Opaque +data: + ANTHROPIC_API_KEY: {{ .Values.apiKeys.anthropic | b64enc | quote }} + OPENAI_API_KEY: {{ .Values.apiKeys.openai | b64enc | quote }} + GEMINI_API_KEY: {{ .Values.apiKeys.gemini | b64enc | quote }} +{{- end }} diff --git a/charts/repowise/templates/service.yaml b/charts/repowise/templates/service.yaml new file mode 100644 index 0000000..5e62740 --- /dev/null +++ b/charts/repowise/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "repowise.fullname" . }} + labels: + {{- include "repowise.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.backendPort }} + targetPort: backend + protocol: TCP + name: backend + - port: {{ .Values.service.frontendPort }} + targetPort: frontend + protocol: TCP + name: frontend + selector: + {{- include "repowise.selectorLabels" . | nindent 4 }} diff --git a/charts/repowise/templates/serviceaccount.yaml b/charts/repowise/templates/serviceaccount.yaml new file mode 100644 index 0000000..19cce09 --- /dev/null +++ b/charts/repowise/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "repowise.serviceAccountName" . }} + labels: + {{- include "repowise.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/repowise/values.yaml b/charts/repowise/values.yaml new file mode 100644 index 0000000..160682f --- /dev/null +++ b/charts/repowise/values.yaml @@ -0,0 +1,178 @@ +# -- Number of replicas (only 1 supported with SQLite; PostgreSQL allows more) +replicaCount: 1 + +image: + # -- Container image repository + repository: repowise/repowise + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Override the image tag (defaults to Chart appVersion) + tag: "" + +# -- Image pull secrets for private registries +imagePullSecrets: [] +# -- Override the release name +nameOverride: "" +# -- Override the full release name +fullnameOverride: "" + +serviceAccount: + # -- Create a ServiceAccount + create: true + # -- Annotations for the ServiceAccount + annotations: {} + # -- Override the ServiceAccount name + name: "" + +# -- Pod-level annotations +podAnnotations: {} + +# -- Pod-level security context +podSecurityContext: + fsGroup: 1000 + +# -- Container-level security context +securityContext: + runAsNonRoot: true + runAsUser: 1000 + +# -- Repowise configuration +repowise: + # -- Embedder to use: mock, openai, gemini, etc. + embedder: mock + # -- Database URL (uses the PVC-backed SQLite by default) + dbUrl: "sqlite+aiosqlite:////data/wiki.db" + # -- Backend API port + backendPort: 7337 + # -- Frontend Web UI port + frontendPort: 3000 + +# -- API keys for LLM providers (stored in a Secret) +apiKeys: + # -- Anthropic API key + anthropic: "" + # -- OpenAI API key + openai: "" + # -- Gemini API key + gemini: "" + +# -- Reference an existing Secret instead of creating one. +# The Secret must contain keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY +existingSecret: "" + +service: + # -- Service type + type: ClusterIP + # -- Backend API port + backendPort: 7337 + # -- Frontend Web UI port + frontendPort: 3000 + +ingress: + # -- Enable Ingress + enabled: false + # -- Ingress class name + className: "" + # -- Ingress annotations + annotations: {} + # kubernetes.io/ingress.class: nginx + # cert-manager.io/cluster-issuer: letsencrypt-prod + # -- Ingress hosts + hosts: + - host: repowise.local + paths: + - path: / + pathType: Prefix + # -- Which port to route to: "frontend" or "backend" + servicePort: frontend + # -- TLS configuration + tls: [] + # - secretName: repowise-tls + # hosts: + # - repowise.local + +persistence: + # -- Enable persistent storage for /data (SQLite DB + indexed repos) + enabled: true + # -- Storage class (leave empty for cluster default) + storageClass: "" + # -- Access mode + accessMode: ReadWriteOnce + # -- Volume size + size: 10Gi + # -- Annotations for the PVC + annotations: {} + +# -- PostgreSQL database (default). Set enabled=false to use SQLite instead. +postgresql: + # -- Enable PostgreSQL (recommended). Disable for SQLite (single-user/dev only). + enabled: true + auth: + # -- PostgreSQL username + username: repowise + # -- PostgreSQL password + password: repowise + # -- PostgreSQL database name + database: repowise + primary: + persistence: + # -- Enable persistent storage for PostgreSQL + enabled: true + # -- Storage class (leave empty for cluster default) + storageClass: "" + # -- Volume size + size: 10Gi + +# -- Repositories to clone and register on install/upgrade. +# A Kubernetes Job runs after deploy to clone each repo into /data/repos/, +# register it with the Repowise API, and trigger an initial sync. +repos: [] + # - name: my-app + # url: https://github.com/org/my-app.git + # branch: main + +# -- Git credentials for cloning private repositories +gitCredentials: + # -- Use an existing Secret with a key "git-credentials" containing: + # https://:@github.com + secretName: "" + github: + # -- GitHub username (used if secretName is empty) + username: "" + # -- GitHub personal access token (used if secretName is empty) + token: "" + +# -- Resource requests and limits +resources: {} + # limits: + # cpu: 1000m + # memory: 2Gi + # requests: + # cpu: 250m + # memory: 512Mi + +# -- Node selector +nodeSelector: {} + +# -- Tolerations +tolerations: [] + +# -- Affinity rules +affinity: {} + +# -- Liveness probe configuration +# Disabled by default: indexing large repos is CPU-intensive and can starve +# the health endpoint, causing unnecessary restarts. Use readiness probe +# instead (it only removes from service, doesn't restart). +# Uncomment below to enable if you don't use heavy indexing. +livenessProbe: {} + +# -- Readiness probe configuration +readinessProbe: + httpGet: + path: /health + port: backend + initialDelaySeconds: 10 + periodSeconds: 15 + timeoutSeconds: 10 + failureThreshold: 10 diff --git a/packages/core/src/repowise/core/persistence/database.py b/packages/core/src/repowise/core/persistence/database.py index c29cd50..88284e2 100644 --- a/packages/core/src/repowise/core/persistence/database.py +++ b/packages/core/src/repowise/core/persistence/database.py @@ -141,8 +141,11 @@ def create_engine( else: kwargs["poolclass"] = NullPool else: - # PostgreSQL — asyncpg handles its own connection pool + # PostgreSQL — increase pool for heavy indexing workloads kwargs["pool_pre_ping"] = True + kwargs["pool_size"] = 200 + kwargs["max_overflow"] = 300 + kwargs["pool_timeout"] = 120 return create_async_engine(db_url, **kwargs)