diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ca66f1..49f06da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## [0.3.0] - to be released +- Support for memos version 0.30.0 +- Add HTTPRoute support +- Support Deployment-Managed Configuration + ## [0.2.1] - 2025-12-07 ### Fixed diff --git a/Chart.yaml b/Chart.yaml index 1e9b0a4..f28d228 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -15,13 +15,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.2.1 +version: 0.3.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.25.2" +appVersion: "0.30.0" home: https://github.com/usememos/helm sources: - https://github.com/usememos/memos diff --git a/README.md b/README.md index 2ceef56..062f999 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,112 @@ when `enabled` is `false` use `emptyDir`, 1. If has created `pvc`, Please change `enabled` to `true`, and change`existPersistClaim`to your `pvc` name. 2. If `enabled` is `true` but `existPersistClaim` is empty,if will create a new `pvc` by `accessMode` `storageClass` `size` +## Database + +Edit `values.yaml` + +```yaml +database: + driver: postgres + existingSecret: + name: memos-cluster-app + key: fqdn-uri +``` + +[Databases](https://usememos.com/docs/configuration/database) configuration can be done with an existing secret (recommended) or by a `database.connectionString`. + +## Application Configuration + +Edit `values.yaml` + +```yaml +settings: + general: + config: + { + "key": "GENERAL", + "generalSetting": + { + "disallowUserRegistration": false, + "disallowPasswordAuth": false, + "additionalScript": "", + "additionalStyle": "", + "weekStartDayOffset": 1, + "disallowChangeUsername": false, + "disallowChangeNickname": false + } + } +``` +The [configuration](https://usememos.com/docs/configuration/deployment-configuration) is generated in `/etc/secrets`. + +All configuration resources are supported. Use either config or an existing secret. + +```yaml +settings: + general: + config: {} + existingSecret: "" + idp-: + config: {} + existingSecret: "" + storage: + config: {} + existingSecret: "" + memo: + config: {} + existingSecret: "" + notification: + config: {} + existingSecret: "" + ai: + config: {} + existingSecret: "" +``` + +An existing secret must have one key named `setting` which contains the configuration as JSON. +As example for an OAuth2 identity provider named `primary-sso` create a secret similar to: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: primary-sso-secret +type: Opaque +stringData: + setting: > + { + "uid": "primary-sso", + "name": "Company SSO", + "type": "OAUTH2", + "identifierFilter": "", + "config": { + "oauth2Config": { + "clientId": "client-id", + "clientSecret": "client-secret", + "authUrl": "https://idp.example.com/oauth/authorize", + "tokenUrl": "https://idp.example.com/oauth/token", + "userInfoUrl": "https://idp.example.com/oauth/userinfo", + "scopes": ["openid", "profile", "email"], + "fieldMapping": { + "identifier": "sub", + "displayName": "name", + "email": "email", + "avatarUrl": "picture" + } + } + } + } +``` + +and define in `values.yaml`: + +```yaml +settings: + idp-primary-sso: + existingSecret: primary-sso-secret +``` + + ## Security The chart includes secure defaults: diff --git a/templates/NOTES.txt b/templates/NOTES.txt index 8a45931..ef29462 100644 --- a/templates/NOTES.txt +++ b/templates/NOTES.txt @@ -1,5 +1,18 @@ 1. Get the application URL by running these commands: -{{- if .Values.ingress.enabled }} +{{- if .Values.httpRoute.enabled }} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} + echo "Visit http(s)://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range .paths }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} @@ -11,7 +24,7 @@ echo http://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "memos.fullname" . }}' + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "memos.fullname" . }}' export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "memos.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP:{{ .Values.service.port }} {{- else if contains "ClusterIP" .Values.service.type }} diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 7b287cd..d7799b5 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -1,7 +1,8 @@ +{{- $fullName := include "memos.fullname" . -}} apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "memos.fullname" . }} + name: {{ $fullName }} labels: {{- include "memos.labels" . | nindent 4 }} spec: @@ -14,8 +15,9 @@ spec: {{- include "memos.selectorLabels" . | nindent 6 }} template: metadata: - {{- with .Values.podAnnotations }} annotations: + checksum/settings: {{ include (print $.Template.BasePath "/settings.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} labels: @@ -32,10 +34,27 @@ spec: - name: memos-data {{- if .Values.persistence.enabled }} persistentVolumeClaim: - claimName: {{ .Values.persistence.existPersistClaim | default (include "memos.fullname" .) }} + claimName: {{ .Values.persistence.existPersistClaim | default ($fullName) }} {{- else }} emptyDir: { } - {{ end }} + {{- end }} + {{- range $key, $section := .Values.settings }} + {{- $hasConfig := and (hasKey $section "config") (not (empty $section.config)) }} + {{- $hasSecret := and (hasKey $section "existingSecret") (not (empty $section.existingSecret)) }} + {{- if or $hasConfig $hasSecret }} + - name: setting-{{ $key }} + {{- if $section.config }} + configMap: + name: {{ $fullName -}}-{{- $key }} + {{- else if $section.existingSecret }} + secret: + secretName: {{ $section.existingSecret }} + {{- end }} + items: + - key: setting + path: setting + {{- end }} + {{- end }} containers: - name: {{ .Chart.Name }} securityContext: @@ -57,10 +76,44 @@ spec: volumeMounts: - name: memos-data mountPath: /var/opt/memos + {{- range $key, $section := .Values.settings }} + {{- $hasConfig := and (hasKey $section "config") (not (empty $section.config)) }} + {{- $hasSecret := and (hasKey $section "existingSecret") (not (empty $section.existingSecret)) }} + {{- if or $hasConfig $hasSecret }} + {{- $fn := "" -}} + {{- if hasPrefix "idp-" $key -}} + {{- $fn = printf "memos-%s.json" $key -}} + {{- else -}} + {{- $fn = printf "memos-instance-setting-%s.json" $key -}} + {{- end }} + - name: setting-{{ $key }} + mountPath: /etc/secrets/{{- $fn }} + subPath: setting + readOnly: true + {{- end }} + {{- end }} # avoid * cannot parse 'Port' as int: strconv.ParseInt: parsing "tcp://10.233.40.219:5230": invalid syntax env: - name: MEMOS_PORT value: "{{ .Values.service.port }}" + - name: MEMOS_LOG_LEVEL + value: {{ .Values.instance.logLevel | quote }} + - name: MEMOS_DRIVER + value: {{ .Values.database.driver | quote }} + {{- if .Values.database.existingSecret }} + - name: MEMOS_DSN + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret.name }} + key: {{ .Values.database.existingSecret.key }} + {{- else if .Values.database.connectionString }} + - name: MEMOS_DSN + value: {{ .Values.database.connectionString | quote }} + {{- end }} + {{- if .Values.instance.url }} + - name: MEMOS_INSTANCE_URL + value: {{ .Values.instance.url }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} diff --git a/templates/httproute.yaml b/templates/httproute.yaml new file mode 100644 index 0000000..c37f98b --- /dev/null +++ b/templates/httproute.yaml @@ -0,0 +1,38 @@ +{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include "memos.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include "memos.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} diff --git a/templates/settings.yaml b/templates/settings.yaml new file mode 100644 index 0000000..91c516e --- /dev/null +++ b/templates/settings.yaml @@ -0,0 +1,22 @@ +{{- if .Values.settings -}} +{{- $fullName := include "memos.fullname" . -}} +{{- $labels := include "memos.labels" . | nindent 4 -}} +{{- range $key, $section := .Values.settings }} +{{- $hasConfig := and (hasKey $section "config") (not (empty $section.config)) }} +{{- $hasSecret := and (hasKey $section "existingSecret") (not (empty $section.existingSecret)) }} +{{- if and $hasConfig $hasSecret }} + {{ fail (printf "Setting '%s' requires either config or existingSecret, not both" $key) }} +{{- else if $hasConfig }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $fullName -}}-{{- $key }} + labels: + {{- $labels }} +data: + setting: > + {{- $section.config | toJson | nindent 4 -}} +{{- end }} +{{- end }} +{{- end }} diff --git a/values.yaml b/values.yaml index d1a8706..ae58d81 100644 --- a/values.yaml +++ b/values.yaml @@ -9,7 +9,7 @@ image: imageName: usememos/memos pullPolicy: IfNotPresent # Overrides the image tag whose default is the chart appVersion. - tag: "latest" + tag: "" imagePullSecrets: [] nameOverride: "" @@ -24,6 +24,47 @@ persistence: accessMode: ReadWriteOnce size: 10Gi +instance: + # Public instance URL, e.g. https://memos.example.com; empty enables private + url: "" + # Log verbosity (debug, info, warn, error) + logLevel: "info" + +database: + # sqlite, mysql, postgres + driver: sqlite + # define database connection string or use an existing secret (recommended) + connectionString: "" + # existingSecret: + # name: + # key: + +settings: + # See https://usememos.com/docs/configuration/deployment-configuration#general + general: + config: {} + existingSecret: null + # See https://usememos.com/docs/configuration/deployment-configuration#oauth2-identity-provider + # idp-: + # config: {} + # existingSecret: + # See https://usememos.com/docs/configuration/deployment-configuration#storage + storage: + config: {} + existingSecret: null + # See https://usememos.com/docs/configuration/deployment-configuration#memo-behavior + memo: + config: {} + existingSecret: null + # See https://usememos.com/docs/configuration/deployment-configuration#ai-providers + notification: + config: {} + existingSecret: null + # See https://usememos.com/docs/configuration/deployment-configuration#ai-providers + ai: + config: {} + existingSecret: "" + serviceAccount: # Specifies whether a service account should be created create: true @@ -71,6 +112,37 @@ ingress: # hosts: # - chart-example.local +httpRoute: + enabled: false + annotations: {} + parentRefs: + - name: gateway + sectionName: http + # namespace: default + # hostnames: + # - chart-example.local + rules: + - matches: + - path: + type: PathPrefix + value: / + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + + resources: limits: cpu: 500m