diff --git a/charts/nextcloud-mcp-server/.cz.toml b/charts/nextcloud-mcp-server/.cz.toml index f9ea19f..409ed6d 100644 --- a/charts/nextcloud-mcp-server/.cz.toml +++ b/charts/nextcloud-mcp-server/.cz.toml @@ -18,7 +18,8 @@ ignored_tag_formats = [ ] # Filter commits by scope +# Includes helm-scoped commits AND MCP server version bumps (which update appVersion) [tool.commitizen.customize] -changelog_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:" +changelog_pattern = "^((feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:|bump: version.*→.*)" schema_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:\\s.+" message_template = "{{change_type}}(helm): {{message}}" diff --git a/charts/nextcloud-mcp-server/templates/_helpers.tpl b/charts/nextcloud-mcp-server/templates/_helpers.tpl index d6656b3..1dcf194 100644 --- a/charts/nextcloud-mcp-server/templates/_helpers.tpl +++ b/charts/nextcloud-mcp-server/templates/_helpers.tpl @@ -72,6 +72,28 @@ Create the name of the secret to use for basic auth {{- end }} {{- end }} +{{/* +Create the name of the secret to use for multi-user basic auth +*/}} +{{- define "nextcloud-mcp-server.multiUserBasicSecretName" -}} +{{- if .Values.auth.multiUserBasic.existingSecret }} +{{- .Values.auth.multiUserBasic.existingSecret }} +{{- else }} +{{- include "nextcloud-mcp-server.fullname" . }}-multi-user-basic +{{- end }} +{{- end }} + +{{/* +Create the name of the PVC to use for multi-user basic token storage +*/}} +{{- define "nextcloud-mcp-server.multiUserBasicPvcName" -}} +{{- if .Values.auth.multiUserBasic.persistence.existingClaim }} +{{- .Values.auth.multiUserBasic.persistence.existingClaim }} +{{- else }} +{{- include "nextcloud-mcp-server.fullname" . }}-token-storage +{{- end }} +{{- end }} + {{/* Create the name of the secret to use for OAuth */}} diff --git a/charts/nextcloud-mcp-server/templates/deployment.yaml b/charts/nextcloud-mcp-server/templates/deployment.yaml index 6471ebb..705b7f1 100644 --- a/charts/nextcloud-mcp-server/templates/deployment.yaml +++ b/charts/nextcloud-mcp-server/templates/deployment.yaml @@ -68,7 +68,7 @@ spec: - name: NEXTCLOUD_HOST value: {{ .Values.nextcloud.host | quote }} {{- if eq .Values.auth.mode "basic" }} - # Basic auth mode + # Basic auth mode (single-user) - name: NEXTCLOUD_USERNAME valueFrom: secretKeyRef: @@ -79,6 +79,41 @@ spec: secretKeyRef: name: {{ include "nextcloud-mcp-server.basicAuthSecretName" . }} key: {{ .Values.auth.basic.passwordKey }} + {{- else if eq .Values.auth.mode "multi-user-basic" }} + # Multi-user BasicAuth mode (pass-through) + - name: ENABLE_MULTI_USER_BASIC_AUTH + value: "true" + - name: NEXTCLOUD_MCP_SERVER_URL + value: {{ include "nextcloud-mcp-server.mcpServerUrl" . | quote }} + - name: NEXTCLOUD_PUBLIC_ISSUER_URL + value: {{ include "nextcloud-mcp-server.publicIssuerUrl" . | quote }} + {{- if .Values.auth.multiUserBasic.enableOfflineAccess }} + # Background operations with app passwords + - name: ENABLE_OFFLINE_ACCESS + value: "true" + - name: TOKEN_STORAGE_DB + value: {{ .Values.auth.multiUserBasic.tokenStorageDb | quote }} + - name: TOKEN_ENCRYPTION_KEY + valueFrom: + secretKeyRef: + name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }} + key: {{ .Values.auth.multiUserBasic.tokenEncryptionKeyKey }} + - name: NEXTCLOUD_OIDC_SCOPES + value: {{ .Values.auth.multiUserBasic.scopes | quote }} + {{- if .Values.auth.multiUserBasic.clientId }} + # Static OAuth credentials (optional - uses DCR if not provided) + - name: NEXTCLOUD_OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }} + key: {{ .Values.auth.multiUserBasic.clientIdKey }} + - name: NEXTCLOUD_OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "nextcloud-mcp-server.multiUserBasicSecretName" . }} + key: {{ .Values.auth.multiUserBasic.clientSecretKey }} + {{- end }} + {{- end }} {{- else if eq .Values.auth.mode "oauth" }} # OAuth mode - name: NEXTCLOUD_MCP_SERVER_URL @@ -251,6 +286,10 @@ spec: - name: oauth-storage mountPath: /app/.oauth {{- end }} + {{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled }} + - name: token-storage + mountPath: /app/data + {{- end }} {{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled }} - name: qdrant-data mountPath: /app/data @@ -266,6 +305,11 @@ spec: persistentVolumeClaim: claimName: {{ include "nextcloud-mcp-server.oauthPvcName" . }} {{- end }} + {{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled }} + - name: token-storage + persistentVolumeClaim: + claimName: {{ include "nextcloud-mcp-server.multiUserBasicPvcName" . }} + {{- end }} {{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled }} - name: qdrant-data persistentVolumeClaim: diff --git a/charts/nextcloud-mcp-server/templates/pvc.yaml b/charts/nextcloud-mcp-server/templates/pvc.yaml index fee7580..f65a100 100644 --- a/charts/nextcloud-mcp-server/templates/pvc.yaml +++ b/charts/nextcloud-mcp-server/templates/pvc.yaml @@ -16,6 +16,24 @@ spec: storage: {{ .Values.auth.oauth.persistence.size }} {{- end }} --- +{{- if and (eq .Values.auth.mode "multi-user-basic") .Values.auth.multiUserBasic.enableOfflineAccess .Values.auth.multiUserBasic.persistence.enabled (not .Values.auth.multiUserBasic.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "nextcloud-mcp-server.fullname" . }}-token-storage + labels: + {{- include "nextcloud-mcp-server.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.auth.multiUserBasic.persistence.accessMode }} + {{- if .Values.auth.multiUserBasic.persistence.storageClass }} + storageClassName: {{ .Values.auth.multiUserBasic.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.auth.multiUserBasic.persistence.size }} +{{- end }} +--- {{- if and (eq .Values.qdrant.mode "persistent") .Values.qdrant.localPersistence.enabled (not .Values.qdrant.localPersistence.existingClaim) }} apiVersion: v1 kind: PersistentVolumeClaim diff --git a/charts/nextcloud-mcp-server/templates/secret.yaml b/charts/nextcloud-mcp-server/templates/secret.yaml index 2039fa3..eed9b81 100644 --- a/charts/nextcloud-mcp-server/templates/secret.yaml +++ b/charts/nextcloud-mcp-server/templates/secret.yaml @@ -13,6 +13,24 @@ data: {{- end }} {{- end }} --- +{{- if eq .Values.auth.mode "multi-user-basic" }} +{{- if and .Values.auth.multiUserBasic.enableOfflineAccess (not .Values.auth.multiUserBasic.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "nextcloud-mcp-server.fullname" . }}-multi-user-basic + labels: + {{- include "nextcloud-mcp-server.labels" . | nindent 4 }} +type: Opaque +data: + {{ .Values.auth.multiUserBasic.tokenEncryptionKeyKey }}: {{ .Values.auth.multiUserBasic.tokenEncryptionKey | b64enc | quote }} + {{- if .Values.auth.multiUserBasic.clientId }} + {{ .Values.auth.multiUserBasic.clientIdKey }}: {{ .Values.auth.multiUserBasic.clientId | b64enc | quote }} + {{ .Values.auth.multiUserBasic.clientSecretKey }}: {{ .Values.auth.multiUserBasic.clientSecret | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} +--- {{- if eq .Values.auth.mode "oauth" }} {{- if and .Values.auth.oauth.clientId (not .Values.auth.oauth.existingSecret) }} apiVersion: v1 diff --git a/charts/nextcloud-mcp-server/values.yaml b/charts/nextcloud-mcp-server/values.yaml index a14a65b..3911c93 100644 --- a/charts/nextcloud-mcp-server/values.yaml +++ b/charts/nextcloud-mcp-server/values.yaml @@ -33,14 +33,15 @@ nextcloud: publicIssuerUrl: "" # Authentication configuration -# Choose either basic auth OR oauth (not both) +# Choose one mode: "basic", "multi-user-basic", or "oauth" auth: - # Authentication mode: "basic" or "oauth" - # basic: Uses username/password (recommended for most users) + # Authentication mode: "basic", "multi-user-basic", or "oauth" + # basic: Single-user with username/password (recommended for personal use) + # multi-user-basic: Multi-user with BasicAuth pass-through (credentials in request headers) # oauth: Uses OAuth2/OIDC (experimental, requires patches) mode: basic - # Basic authentication settings + # Basic authentication settings (single-user mode) basic: # Nextcloud username (ignored if existingSecret is set) username: "" @@ -58,6 +59,47 @@ auth: usernameKey: "username" passwordKey: "password" + # Multi-user BasicAuth settings (pass-through mode) + # Users provide credentials in request headers (Authorization: Basic ...) + # Server optionally stores app passwords for background operations + multiUserBasic: + # Enable offline access (background operations using app passwords via Astrolabe) + # When enabled, requires token encryption key. OAuth client credentials are optional (uses DCR if not provided) + enableOfflineAccess: false + # Token encryption key (required if enableOfflineAccess: true, ignored if existingSecret is set) + # Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" + tokenEncryptionKey: "" + # Token storage database path + tokenStorageDb: "/app/data/tokens.db" + # OAuth client credentials (optional - uses Dynamic Client Registration if not provided) + # Only needed if enableOfflineAccess: true + clientId: "" + clientSecret: "" + # OAuth scopes to request (space-separated) + scopes: "openid profile email offline_access notes:read notes:write calendar:read calendar:write contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write tables:read tables:write files:read files:write sharing:read sharing:write todo:read todo:write" + # Use existing secret for multi-user basic auth credentials + # If set, tokenEncryptionKey, clientId, and clientSecret above are ignored + # Secret should contain keys specified in the *Key fields below + # Example: + # kubectl create secret generic my-multiuser-creds \ + # --from-literal=token_encryption_key=ESF1BvEQ... \ + # --from-literal=client_id=my-client-id \ + # --from-literal=client_secret=my-client-secret + existingSecret: "" + # Keys in the existing secret + tokenEncryptionKeyKey: "token_encryption_key" + clientIdKey: "client_id" + clientSecretKey: "client_secret" + # Persistent storage for token database + persistence: + enabled: true + # Storage class (leave empty for default) + storageClass: "" + accessMode: ReadWriteOnce + size: 100Mi + # Use existing PVC + existingClaim: "" + # OAuth2/OIDC settings (experimental) oauth: # OAuth token type: "jwt" or "opaque"