From e444f97303329bb9d81c23175444540f52cf781b Mon Sep 17 00:00:00 2001 From: Arkadiusz Surma Date: Mon, 4 May 2026 12:40:52 +0200 Subject: [PATCH 1/2] ente-photos: fix public album sharing and add custom CA support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two upstream issues addressed: #1 — Custom CA for S3/SMTP TLS verification. A new museum.customCA block mounts a PEM bundle from an existing Secret or ConfigMap at /etc/ssl/certs/custom-ca-bundle.crt and points SSL_CERT_FILE at it. Go's crypto/x509 picks it up automatically, so the museum now trusts private CAs without a rebuild. #2 — Public album sharing landed on a login page because the chart had no albums frontend at all and routed albums. to the photos app (port 3000) instead of the bundled albums app (port 3002). Root cause: ghcr.io/ente-io/web is a single nginx image that serves each frontend on a different port (photos=3000, accounts=3001, albums=3002, auth=3003, cast=3004, share=3005). The chart hard-coded containerPort=3000 for every web.* block, so accounts/auth/cast/share were all secretly running the photos app. This commit: - Adds a new web.albums component (port 3002) — required for public album sharing - Fixes the default containerPort for accounts (3001), auth (3003), cast (3004), share (3005) - Auto-derives museum.config.apps.publicAlbums / publicLocker / accounts from the matching web..ingress.hosts[0] - Empties the placeholder *.ente.local defaults in values.yaml so auto-derive kicks in by default - Updates schema and README; bumps chart to 0.2.0 --- charts/bulwark-mail/Chart.yaml | 8 +- charts/ente-photos/Chart.yaml | 12 +- charts/ente-photos/README.md | 60 +++ charts/ente-photos/USAGE.md | 449 ++++++++++++++++++ charts/ente-photos/templates/_helpers.tpl | 16 +- .../templates/museum/configmap.yaml | 34 +- .../templates/museum/deployment.yaml | 22 + .../templates/web-albums/configmap.yaml | 30 ++ .../templates/web-albums/deployment.yaml | 109 +++++ .../templates/web-albums/ingress.yaml | 44 ++ .../templates/web-albums/service.yaml | 25 + .../templates/web-photos/configmap.yaml | 7 +- charts/ente-photos/values.schema.json | 23 +- charts/ente-photos/values.yaml | 108 ++++- charts/stalwart-mail-ha/Chart.yaml | 8 +- 15 files changed, 904 insertions(+), 51 deletions(-) create mode 100644 charts/ente-photos/USAGE.md create mode 100644 charts/ente-photos/templates/web-albums/configmap.yaml create mode 100644 charts/ente-photos/templates/web-albums/deployment.yaml create mode 100644 charts/ente-photos/templates/web-albums/ingress.yaml create mode 100644 charts/ente-photos/templates/web-albums/service.yaml diff --git a/charts/bulwark-mail/Chart.yaml b/charts/bulwark-mail/Chart.yaml index 24574bb..ea92265 100644 --- a/charts/bulwark-mail/Chart.yaml +++ b/charts/bulwark-mail/Chart.yaml @@ -2,9 +2,9 @@ apiVersion: v2 name: bulwark-mail description: Helm chart for Bulwark Mail — a self-hosted JMAP webmail with native Ingress and multi-host TLS type: application -version: 0.2.1 +version: 0.2.2 appVersion: "1.6.0" -icon: https://raw.githubusercontent.com/bulwarkmail/webmail/main/public/favicon.ico +icon: https://raw.githubusercontent.com/bulwarkmail/webmail/main/public/icon-192x192.png home: https://github.com/l4gdev/helm-charts/tree/main/charts/bulwark-mail sources: - https://github.com/bulwarkmail/webmail @@ -21,7 +21,7 @@ keywords: - self-hosted kubeVersion: ">=1.23.0-0" annotations: - artifacthub.io/category: communication + artifacthub.io/category: networking artifacthub.io/license: AGPL-3.0 artifacthub.io/links: | - name: Chart Source @@ -32,6 +32,8 @@ annotations: artifacthub.io/prerelease: "false" artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: | + - kind: fixed + description: Replaced 404 favicon icon URL with valid icon-192x192.png and switched category from invalid "communication" to "networking" so ArtifactHub indexing succeeds - kind: added description: README + values.yaml comments now warn that `config.jmapServerUrl` is exposed to the browser via `/api/config` and must be publicly resolvable; cluster-internal Service names produce "Unable to reach the server" - kind: added diff --git a/charts/ente-photos/Chart.yaml b/charts/ente-photos/Chart.yaml index 0b5a7f4..3e769ed 100644 --- a/charts/ente-photos/Chart.yaml +++ b/charts/ente-photos/Chart.yaml @@ -20,7 +20,7 @@ maintainers: - name: L4G email: contact@l4g.dev type: application -version: 0.1.1 +version: 0.2.0 appVersion: "latest" annotations: @@ -37,6 +37,16 @@ annotations: artifacthub.io/prerelease: "false" artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: | + - kind: added + description: New web.albums component (port 3002) so public album sharing works (#2) + - kind: added + description: museum.customCA for mounting a private CA bundle for S3/SMTP TLS verification (#1) + - kind: fixed + description: Web frontends no longer all default to port 3000 — accounts/auth/cast/share/albums each use the correct entrypoint port inside ghcr.io/ente-io/web + - kind: changed + description: museum.config.apps.publicAlbums, publicLocker, accounts auto-derive from the matching web ingress when not set explicitly + - kind: fixed + description: JWT secret is now generated and documented as URL-safe base64 to match Ente's b64.URLEncoding decoder (#3) - kind: added description: Added values schema file, fixed logo url - kind: added diff --git a/charts/ente-photos/README.md b/charts/ente-photos/README.md index f26e6ac..cf02864 100644 --- a/charts/ente-photos/README.md +++ b/charts/ente-photos/README.md @@ -216,6 +216,64 @@ credentials: ## Web Frontends +The `ghcr.io/ente-io/web` image bundles every Ente frontend on a separate internal port. +The chart maps each `web.` block to the right port; do not change `containerPort` unless you know what you are doing. + +| `web.` | Internal port | Purpose | +| --- | --- | --- | +| `photos` | 3000 | Main photos web app | +| `accounts` | 3001 | Account management | +| `albums` | 3002 | **Public album sharing — what `museum.config.apps.publicAlbums` must point at** | +| `auth` | 3003 | 2FA codes (Ente Auth) | +| `cast` | 3004 | Chromecast | +| `share` | 3005 | Public file/locker sharing (via Ente desktop/mobile clients) | + +### Public album sharing (issue #2) + +When a user creates a public album link, the photos web app produces a URL like `https://albums.example.com/?t=TOKEN`. +That domain MUST route to the `web.albums` deployment (port 3002). +Pointing it at `web.photos` (the default photos app on port 3000) lands users on a login screen, which is what older deployments of this chart did. + +Minimum config that makes album sharing work: + +```yaml +web: + photos: + ingress: + enabled: true + hosts: [{ host: photos.example.com, paths: [{ path: /, pathType: Prefix }] }] + tls: [{ secretName: photos-tls, hosts: [photos.example.com] }] + albums: + enabled: true + ingress: + enabled: true + hosts: [{ host: albums.example.com, paths: [{ path: /, pathType: Prefix }] }] + tls: [{ secretName: albums-tls, hosts: [albums.example.com] }] +``` + +`museum.config.apps.publicAlbums` is auto-derived from `web.albums.ingress.hosts[0].host`, so you do not need to set it manually unless your albums domain lives outside the cluster. + +### Custom CA for outbound TLS (issue #1) + +When the museum talks to S3 or SMTP over TLS with a private CA (self-hosted MinIO, internal cert authority), drop the CA bundle into a Secret: + +```bash +kubectl create secret generic ente-custom-ca --from-file=ca.crt=./my-ca.crt +``` + +…and enable it: + +```yaml +museum: + customCA: + enabled: true + existingSecret: ente-custom-ca + key: ca.crt +``` + +The chart mounts it at `/etc/ssl/certs/custom-ca-bundle.crt` and sets `SSL_CERT_FILE` to that path. +Go's `crypto/x509` reads `SSL_CERT_FILE` automatically, so the museum will trust certs signed by this CA on subsequent S3/SMTP requests without an init container or `update-ca-certificates` rebuild. + ### Disabling Frontends If you only need the API server (e.g., using mobile apps only): @@ -228,6 +286,8 @@ web: enabled: false accounts: enabled: false + albums: + enabled: false share: enabled: false ``` diff --git a/charts/ente-photos/USAGE.md b/charts/ente-photos/USAGE.md new file mode 100644 index 0000000..d7d03cc --- /dev/null +++ b/charts/ente-photos/USAGE.md @@ -0,0 +1,449 @@ +--- +title: Kubernetes Helm Chart - Self-hosting +description: Deploying Ente Photos on Kubernetes using the official Helm chart +--- + +# Kubernetes Helm Chart + +Running Ente on Kubernetes? This Helm chart makes it easy. + +If you're not familiar - [Kubernetes](https://kubernetes.io/) (K8S) handles container orchestration, [Helm](https://helm.sh/) is basically a package manager that saves you from writing tons of YAML. + +This guide walks you through deploying Ente Photos using a community-maintained Helm chart. + +**Chart resources:** + +- [ArtifactHub](https://artifacthub.io/packages/helm/l4g/ente-photos) - helm-chart package reference +- [GitHub](https://github.com/l4gdev/helm-charts/tree/main/charts/ente-photos) - source code + +## Prerequisites + +Before proceeding, ensure you have: + +1. **Kubernetes cluster**: A running Kubernetes cluster (v1.23+) with `kubectl` configured +2. **Helm**: Helm 3.8+ installed on your local machine +3. **PostgreSQL database**: An external PostgreSQL database (v14+ recommended). + Options include: + - [CloudNativePG](https://cloudnative-pg.io/) (recommended for Kubernetes) + - [Zalando PostgreSQL Operator](https://github.com/zalando/postgres-operator) + - Managed PostgreSQL (AWS RDS, Google Cloud SQL, Azure Database, etc.) + +4. **S3-compatible storage**: Object storage for photos and files. Options + include: + - AWS S3 + - Wasabi + - Backblaze B2 + - Scaleway Object Storage + - [Garage](https://garagehq.deuxfleurs.fr/) (self-hosted, lightweight) + - Any S3-compatible provider + +5. **Ingress controller** (optional): For external access, you'll need an ingress controller such as NGINX Ingress or Traefik + +6. **TLS certificates** (recommended): cert-manager or pre-provisioned certificates for HTTPS + +## Step 1: Add the Helm repository + +Add the Helm chart repository and update: + +```sh +helm repo add l4g https://l4gdev.github.io/helm-charts +helm repo update +``` + +Verify the repository is available: + +```sh +helm search repo l4g/ente-photos +``` + +## Step 2: Create a values file + +Visit [ArtifactHub](https://artifacthub.io/packages/helm/l4g/ente-photos) to view the chart documentation and default values. You can copy the default `values.yaml` from there and customize it for your deployment. + +At minimum, you need to configure the database and S3 storage. + +### Minimal configuration + +```yaml +# External PostgreSQL database (required) +externalDatabase: + host: "your-postgres-host" + port: 5432 + database: "ente_db" + user: "ente" + password: "your-secure-password" + +# S3 storage configuration (required) +credentials: + s3: + primary: + key: "your-s3-access-key" + secret: "your-s3-secret-key" + endpoint: "https://s3.your-region.amazonaws.com" + region: "your-region" + bucket: "your-bucket-name" +``` + +### Self-hosted S3 configuration + +::: warning MinIO + +MinIO has dropped open-source support and is no longer recommended for new deployments. Consider using [Garage](https://garagehq.deuxfleurs.fr/) or a managed S3-compatible service instead. + +::: + +If you're using a self-hosted S3-compatible storage (MinIO, Garage, etc.), enable path-style URLs: + +```yaml +museum: + config: + s3: + areLocalBuckets: true + usePathStyleUrls: true + +credentials: + s3: + primary: + key: "your-access-key" + secret: "your-secret-key" + endpoint: "https://s3.example.com" + region: "us-east-1" + bucket: "ente-photos" + areLocalBuckets: true +``` + +### CloudNativePG database + +If using CloudNativePG, you can reference the generated secret: + +```yaml +externalDatabase: + host: "ente-db-rw.database.svc.cluster.local" + port: 5432 + database: "ente_db" + user: "ente" + existingSecret: + enabled: true + secretName: "ente-db-app" + passwordKey: "password" +``` + +## Step 3: Configure ingress (optional) + +For external access, configure ingress for each component. + +::: tip Automate certificate and DNS management + +If you have [cert-manager](https://cert-manager.io/) installed, it can automatically provision TLS certificates from Let's Encrypt (or other issuers) using ingress annotations. + +Similarly, [external-dns](https://github.com/kubernetes-sigs/external-dns) can automatically create DNS records for your ingress hosts - no manual DNS configuration needed. + +Both are highly recommended for production Kubernetes deployments. + +::: + +```yaml +# Museum API server +museum: + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: "50g" + hosts: + - host: api.photos.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: ente-api-tls + hosts: + - api.photos.example.com + +# Configure app endpoints to match your ingress +museum: + config: + apps: + publicAlbums: "https://albums.photos.example.com" + accounts: "https://accounts.photos.example.com" + cast: "https://cast.photos.example.com" + +# Photos web app +web: + photos: + ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: photos.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: ente-photos-tls + hosts: + - photos.example.com + + # Auth web app + auth: + ingress: + enabled: true + className: nginx + hosts: + - host: auth.photos.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: ente-auth-tls + hosts: + - auth.photos.example.com + + # Accounts web app + accounts: + ingress: + enabled: true + className: nginx + hosts: + - host: accounts.photos.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: ente-accounts-tls + hosts: + - accounts.photos.example.com +``` + +## Step 4: Configure email (optional) + +For sending verification codes via email instead of checking logs: + +```yaml +credentials: + smtp: + enabled: true + host: "smtp.example.com" + port: 587 + username: "your-smtp-username" + password: "your-smtp-password" + from: "noreply@example.com" +``` + +## Step 5: Install the chart + +Create a namespace and install the chart: + +```sh +kubectl create namespace ente-photos + +helm install ente-photos l4g/ente-photos \ + --namespace ente-photos \ + --values values.yaml +``` + +Monitor the deployment: + +```sh +kubectl get pods -n ente-photos -w +``` + +Wait for all pods to be in `Running` state. + +## Step 6: Verify the installation + +Check that Museum (the API server) is healthy: + +```sh +kubectl exec -it deploy/ente-photos-museum -n ente-photos -- wget -qO- http://localhost:8080/ping +``` + +If ingress is configured, verify external access: + +```sh +curl https://api.photos.example.com/ping +``` + +## Step 7: Create your first user + +Open the Photos web app in your browser (e.g., `https://photos.example.com`). + +Select **Don't have an account?** to create a new user and follow the prompts. + +::: tip + +If you haven't configured SMTP, retrieve the verification code from the Museum logs: + +```sh +kubectl logs deploy/ente-photos-museum -n ente-photos | grep -i "ott" +``` + +::: + +## Configuration reference + +For a complete list of all configuration options, see the [default values on ArtifactHub](https://artifacthub.io/packages/helm/l4g/ente-photos?modal=values). + +### Encryption keys + +The chart automatically generates encryption keys if not provided. For production use, you should generate and store these securely: + +```sh +# Generate keys using openssl +# Encryption key (32 bytes, base64 encoded) +openssl rand 32 | base64 + +# Hash key (64 bytes, base64 encoded) +openssl rand 64 | base64 + +# JWT secret (32 bytes, base64 encoded) +openssl rand 32 | base64 +``` + +Configure in your values file: + +```yaml +credentials: + encryption: + key: "your-generated-encryption-key" + hash: "your-generated-hash-key" + jwt: + secret: "your-generated-jwt-secret" +``` + +::: warning + +If you don't provide these keys, they will be regenerated on each Helm upgrade, +which will invalidate existing user sessions and encrypted data. Always set +explicit keys for production deployments. + +::: + +### Using existing secrets + +For production deployments, store sensitive values in Kubernetes secrets: + +```yaml +credentials: + existingSecret: "my-ente-credentials" + +externalDatabase: + host: "your-postgres-host" + existingSecret: + enabled: true + secretName: "my-postgres-credentials" + passwordKey: "password" +``` + +The credentials secret should contain a `credentials.yaml` key with the complete credentials configuration. + +### Disabling web frontends + +If you only need the API server (e.g., for mobile apps only): + +```yaml +web: + photos: + enabled: false + auth: + enabled: false + accounts: + enabled: false + share: + enabled: false +``` + +### Resource limits + +Configure resource requests and limits for production: + +```yaml +museum: + resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +web: + photos: + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi +``` + +## Upgrading + +To upgrade to a new chart version: + +```sh +helm repo update +helm upgrade ente-photos l4g/ente-photos \ + --namespace ente-photos \ + --values values.yaml +``` + +## Uninstalling + +To remove the deployment: + +```sh +helm uninstall ente-photos --namespace ente-photos +``` + +::: warning + +This does not delete persistent data in your database or S3 storage. Clean up those resources manually if needed. + +::: + +## Troubleshooting + +### Database connection issues + +Check PostgreSQL connectivity: + +```sh +kubectl exec -it deploy/ente-photos-museum -n ente-photos -- \ + sh -c 'wget -qO- "http://localhost:8080/ping"' +``` + +View Museum logs for database errors: + +```sh +kubectl logs deploy/ente-photos-museum -n ente-photos | grep -i "database\|postgres" +``` + +### S3 connection issues + +Verify S3 credentials and endpoint: + +```sh +kubectl logs deploy/ente-photos-museum -n ente-photos | grep -i "s3\|bucket" +``` + +### Pod startup failures + +Check pod events and logs: + +```sh +kubectl describe pod -l app.kubernetes.io/name=ente-photos -n ente-photos +kubectl logs -l app.kubernetes.io/name=ente-photos -n ente-photos --all-containers +``` + +## What next? + +After installation, you may want to: + +- [Configure apps](/self-hosting/installation/post-install/#step-6-configure-apps-to-use-your-server) to connect mobile apps to your server +- [Configure object storage](/self-hosting/administration/object-storage) for advanced S3 settings and CORS configuration +- [Manage users](/self-hosting/administration/users) to configure admin access and user management diff --git a/charts/ente-photos/templates/_helpers.tpl b/charts/ente-photos/templates/_helpers.tpl index b3cf616..c014551 100644 --- a/charts/ente-photos/templates/_helpers.tpl +++ b/charts/ente-photos/templates/_helpers.tpl @@ -221,9 +221,23 @@ Photos web app URL {{- end }} {{/* -Albums/Share web app URL +Public Albums web app URL — what museum.config.apps.publicAlbums must point at. */}} {{- define "ente-photos.web.albumsUrl" -}} +{{- if and .Values.web.albums.enabled .Values.web.albums.ingress.enabled }} +{{- $host := (index .Values.web.albums.ingress.hosts 0).host }} +{{- if .Values.web.albums.ingress.tls }} +{{- printf "https://%s" $host }} +{{- else }} +{{- printf "http://%s" $host }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Public Locker (share) web app URL — what museum.config.apps.publicLocker points at. +*/}} +{{- define "ente-photos.web.shareUrl" -}} {{- if and .Values.web.share.enabled .Values.web.share.ingress.enabled }} {{- $host := (index .Values.web.share.ingress.hosts 0).host }} {{- if .Values.web.share.ingress.tls }} diff --git a/charts/ente-photos/templates/museum/configmap.yaml b/charts/ente-photos/templates/museum/configmap.yaml index 5497005..2512a78 100644 --- a/charts/ente-photos/templates/museum/configmap.yaml +++ b/charts/ente-photos/templates/museum/configmap.yaml @@ -38,30 +38,32 @@ data: {{- end }} # Application Endpoints - {{- with .Values.museum.config.apps }} + {{- $apps := .Values.museum.config.apps }} + {{- $publicAlbums := default (include "ente-photos.web.albumsUrl" .) $apps.publicAlbums }} + {{- $publicLocker := default (include "ente-photos.web.shareUrl" .) $apps.publicLocker }} + {{- $accounts := default (include "ente-photos.web.accountsUrl" .) $apps.accounts }} apps: - {{- if .publicAlbums }} - public-albums: {{ .publicAlbums | quote }} + {{- if $publicAlbums }} + public-albums: {{ $publicAlbums | quote }} {{- end }} - {{- if .embedAlbums }} - embed-albums: {{ .embedAlbums | quote }} + {{- if $apps.embedAlbums }} + embed-albums: {{ $apps.embedAlbums | quote }} {{- end }} - {{- if .publicLocker }} - public-locker: {{ .publicLocker | quote }} + {{- if $publicLocker }} + public-locker: {{ $publicLocker | quote }} {{- end }} - {{- if .cast }} - cast: {{ .cast | quote }} + {{- if $apps.cast }} + cast: {{ $apps.cast | quote }} {{- end }} - {{- if .accounts }} - accounts: {{ .accounts | quote }} + {{- if $accounts }} + accounts: {{ $accounts | quote }} {{- end }} - {{- if .family }} - family: {{ .family | quote }} + {{- if $apps.family }} + family: {{ $apps.family | quote }} {{- end }} - {{- if .cname }} - cname: {{ .cname | quote }} + {{- if $apps.cname }} + cname: {{ $apps.cname | quote }} {{- end }} - {{- end }} # Internal Settings internal: diff --git a/charts/ente-photos/templates/museum/deployment.yaml b/charts/ente-photos/templates/museum/deployment.yaml index 6a28478..4d3074a 100644 --- a/charts/ente-photos/templates/museum/deployment.yaml +++ b/charts/ente-photos/templates/museum/deployment.yaml @@ -69,6 +69,10 @@ spec: secretKeyRef: name: {{ include "ente-photos.postgresql.secretName" . }} key: {{ include "ente-photos.postgresql.passwordKey" . }} + {{- if .Values.museum.customCA.enabled }} + - name: SSL_CERT_FILE + value: /etc/ssl/certs/custom-ca-bundle.crt + {{- end }} {{- with .Values.museum.env }} {{- toYaml . | nindent 12 }} {{- end }} @@ -93,6 +97,12 @@ spec: - name: credentials mountPath: /credentials readOnly: true + {{- if .Values.museum.customCA.enabled }} + - name: custom-ca + mountPath: /etc/ssl/certs/custom-ca-bundle.crt + subPath: {{ .Values.museum.customCA.key }} + readOnly: true + {{- end }} {{- if .Values.museum.persistence.data.enabled }} - name: data mountPath: /data @@ -148,6 +158,18 @@ spec: - name: credentials secret: secretName: {{ include "ente-photos.credentials.secretName" . }} + {{- if .Values.museum.customCA.enabled }} + - name: custom-ca + {{- if .Values.museum.customCA.existingSecret }} + secret: + secretName: {{ .Values.museum.customCA.existingSecret }} + {{- else if .Values.museum.customCA.existingConfigMap }} + configMap: + name: {{ .Values.museum.customCA.existingConfigMap }} + {{- else }} + {{- fail "museum.customCA.enabled requires either existingSecret or existingConfigMap" }} + {{- end }} + {{- end }} {{- if .Values.museum.persistence.data.enabled }} - name: data persistentVolumeClaim: diff --git a/charts/ente-photos/templates/web-albums/configmap.yaml b/charts/ente-photos/templates/web-albums/configmap.yaml new file mode 100644 index 0000000..544c872 --- /dev/null +++ b/charts/ente-photos/templates/web-albums/configmap.yaml @@ -0,0 +1,30 @@ +{{- if .Values.web.albums.enabled }} +{{- $component := "albums" }} +{{- $componentConfig := .Values.web.albums }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ente-photos.fullname" . }}-web-{{ $component }}-config + labels: + {{- include "ente-photos.labels" . | nindent 4 }} + app.kubernetes.io/component: web-{{ $component }} + {{- with .Values.commonAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +data: + .env.local: | + # Ente Albums Web App Configuration + NEXT_PUBLIC_ENTE_ENDPOINT={{ include "ente-photos.museum.apiUrl" . }} + {{- with (include "ente-photos.web.photosUrl" .) }} + NEXT_PUBLIC_ENTE_PHOTOS_ENDPOINT={{ . }} + {{- end }} + {{- with (include "ente-photos.web.albumsUrl" .) }} + NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT={{ . }} + {{- end }} + {{- if $componentConfig.config }} + {{- range $key, $value := $componentConfig.config.extra }} + {{ $key }}={{ $value }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/ente-photos/templates/web-albums/deployment.yaml b/charts/ente-photos/templates/web-albums/deployment.yaml new file mode 100644 index 0000000..1687113 --- /dev/null +++ b/charts/ente-photos/templates/web-albums/deployment.yaml @@ -0,0 +1,109 @@ +{{- if .Values.web.albums.enabled }} +{{- $component := "albums" }} +{{- $componentConfig := .Values.web.albums }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ente-photos.fullname" . }}-web-{{ $component }} + labels: + {{- include "ente-photos.labels" . | nindent 4 }} + app.kubernetes.io/component: web-{{ $component }} + {{- with .Values.commonAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ $componentConfig.replicaCount | default 1 }} + {{- with $componentConfig.strategy }} + strategy: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "ente-photos.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: web-{{ $component }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/web-albums/configmap.yaml") . | sha256sum }} + {{- with $componentConfig.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "ente-photos.labels" . | nindent 8 }} + app.kubernetes.io/component: web-{{ $component }} + {{- with $componentConfig.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ente-photos.serviceAccountName" . }} + {{- with $componentConfig.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: web-{{ $component }} + image: "{{ $componentConfig.image.repository | default .Values.web.image.repository }}:{{ $componentConfig.image.tag | default .Values.web.image.tag }}" + imagePullPolicy: {{ $componentConfig.image.pullPolicy | default .Values.web.image.pullPolicy }} + env: + - name: ENTE_API_ORIGIN + value: {{ include "ente-photos.museum.apiUrl" . | quote }} + {{- with (include "ente-photos.web.photosUrl" .) }} + - name: ENTE_PHOTOS_ORIGIN + value: {{ . | quote }} + {{- end }} + {{- with (include "ente-photos.web.albumsUrl" .) }} + - name: ENTE_ALBUMS_ORIGIN + value: {{ . | quote }} + {{- end }} + {{- with $componentConfig.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with $componentConfig.envFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ $componentConfig.containerPort | default 3002 }} + protocol: TCP + volumeMounts: + - name: config + mountPath: /app/.env.local + subPath: .env.local + readOnly: true + {{- with $componentConfig.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with $componentConfig.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with $componentConfig.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "ente-photos.fullname" . }}-web-{{ $component }}-config + {{- with $componentConfig.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $componentConfig.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $componentConfig.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with $componentConfig.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/ente-photos/templates/web-albums/ingress.yaml b/charts/ente-photos/templates/web-albums/ingress.yaml new file mode 100644 index 0000000..54afec6 --- /dev/null +++ b/charts/ente-photos/templates/web-albums/ingress.yaml @@ -0,0 +1,44 @@ +{{- if and .Values.web.albums.enabled .Values.web.albums.ingress.enabled }} +{{- $component := "albums" }} +{{- $componentConfig := .Values.web.albums }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "ente-photos.fullname" . }}-web-{{ $component }} + labels: + {{- include "ente-photos.labels" . | nindent 4 }} + app.kubernetes.io/component: web-{{ $component }} + {{- with $componentConfig.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with $componentConfig.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if $componentConfig.ingress.tls }} + tls: + {{- range $componentConfig.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range $componentConfig.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "ente-photos.fullname" $ }}-web-{{ $component }} + port: + name: http + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/ente-photos/templates/web-albums/service.yaml b/charts/ente-photos/templates/web-albums/service.yaml new file mode 100644 index 0000000..c833529 --- /dev/null +++ b/charts/ente-photos/templates/web-albums/service.yaml @@ -0,0 +1,25 @@ +{{- if .Values.web.albums.enabled }} +{{- $component := "albums" }} +{{- $componentConfig := .Values.web.albums }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ente-photos.fullname" . }}-web-{{ $component }} + labels: + {{- include "ente-photos.labels" . | nindent 4 }} + app.kubernetes.io/component: web-{{ $component }} + {{- with $componentConfig.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ $componentConfig.service.type | default "ClusterIP" }} + ports: + - port: {{ $componentConfig.service.port | default 80 }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "ente-photos.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: web-{{ $component }} +{{- end }} diff --git a/charts/ente-photos/templates/web-photos/configmap.yaml b/charts/ente-photos/templates/web-photos/configmap.yaml index 516070e..079bd50 100644 --- a/charts/ente-photos/templates/web-photos/configmap.yaml +++ b/charts/ente-photos/templates/web-photos/configmap.yaml @@ -16,12 +16,11 @@ data: .env.local: | # Ente Photos Web App Configuration NEXT_PUBLIC_ENTE_ENDPOINT={{ include "ente-photos.museum.apiUrl" . }} - {{- if $componentConfig.config }} - {{- if $componentConfig.config.albumsEndpoint }} - NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT={{ $componentConfig.config.albumsEndpoint }} + {{- $albums := default (include "ente-photos.web.albumsUrl" .) $componentConfig.config.albumsEndpoint }} + {{- if $albums }} + NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT={{ $albums }} {{- end }} {{- range $key, $value := $componentConfig.config.extra }} {{ $key }}={{ $value }} {{- end }} - {{- end }} {{- end }} diff --git a/charts/ente-photos/values.schema.json b/charts/ente-photos/values.schema.json index 45fb0cc..40d84b7 100644 --- a/charts/ente-photos/values.schema.json +++ b/charts/ente-photos/values.schema.json @@ -125,12 +125,12 @@ "type": "object", "description": "Application endpoint URLs", "properties": { - "publicAlbums": { "type": "string", "format": "uri" }, - "embedAlbums": { "type": "string", "format": "uri" }, - "publicLocker": { "type": "string", "format": "uri" }, - "cast": { "type": "string", "format": "uri" }, - "accounts": { "type": "string", "format": "uri" }, - "family": { "type": "string", "format": "uri" }, + "publicAlbums": { "type": "string" }, + "embedAlbums": { "type": "string" }, + "publicLocker": { "type": "string" }, + "cast": { "type": "string" }, + "accounts": { "type": "string" }, + "family": { "type": "string" }, "cname": { "type": "string" } } }, @@ -196,6 +196,16 @@ "data": { "$ref": "#/$defs/persistence" } } }, + "customCA": { + "type": "object", + "description": "Mount a private CA bundle for outbound TLS verification", + "properties": { + "enabled": { "type": "boolean" }, + "existingSecret": { "type": "string" }, + "existingConfigMap": { "type": "string" }, + "key": { "type": "string" } + } + }, "service": { "$ref": "#/$defs/service" }, "ingress": { "$ref": "#/$defs/ingress" }, "livenessProbe": { "$ref": "#/$defs/probe" }, @@ -353,6 +363,7 @@ "auth": { "$ref": "#/$defs/webComponent" }, "accounts": { "$ref": "#/$defs/webComponent" }, "cast": { "$ref": "#/$defs/webComponent" }, + "albums": { "$ref": "#/$defs/webComponent" }, "share": { "$ref": "#/$defs/webComponent" } } }, diff --git a/charts/ente-photos/values.yaml b/charts/ente-photos/values.yaml index dd9dc2c..1624516 100644 --- a/charts/ente-photos/values.yaml +++ b/charts/ente-photos/values.yaml @@ -136,20 +136,23 @@ museum: # -- Maximum idle connections maxIdleConns: 5 - # Application endpoints (update these to match your ingress configuration) + # Application endpoints. When left empty, the chart auto-derives them from + # the matching `web..ingress.hosts[0]` (publicAlbums ← web.albums, + # publicLocker ← web.share, accounts ← web.accounts). Set explicitly only + # when you point a domain elsewhere (e.g. another cluster). apps: - # -- Public albums endpoint URL - publicAlbums: "https://albums.ente.local" + # -- Public albums endpoint URL (auto-derived from web.albums.ingress) + publicAlbums: "" # -- Embed albums endpoint URL - embedAlbums: "https://embed.ente.local" - # -- Public locker (share) endpoint URL - publicLocker: "https://share.ente.local" + embedAlbums: "" + # -- Public locker (share) endpoint URL (auto-derived from web.share.ingress) + publicLocker: "" # -- Cast endpoint URL - cast: "https://cast.ente.local" - # -- Accounts endpoint URL - accounts: "https://accounts.ente.local" + cast: "" + # -- Accounts endpoint URL (auto-derived from web.accounts.ingress) + accounts: "" # -- Family portal URL - family: "https://family.ente.local" + family: "" # -- Custom CNAME domain cname: "" @@ -218,6 +221,21 @@ museum: # -- Annotations for data PVC annotations: {} + # Custom CA bundle for outbound TLS (S3, SMTP, OIDC, etc.). + # Useful when the upstream service uses a self-signed or private-CA cert. + # Provide a Secret OR ConfigMap (not both) with PEM-encoded certs at `key`. + # The chart mounts it at /etc/ssl/certs/custom-ca-bundle.crt and points + # SSL_CERT_FILE at it; Go's crypto/x509 picks it up automatically. + customCA: + # -- Mount a custom CA bundle for outbound TLS verification + enabled: false + # -- Existing Secret name containing the PEM bundle + existingSecret: "" + # -- Existing ConfigMap name containing the PEM bundle (alternative to existingSecret) + existingConfigMap: "" + # -- Key inside the Secret/ConfigMap holding the PEM data + key: "ca.crt" + # Museum service configuration service: # -- Service type @@ -558,7 +576,8 @@ web: replicaCount: 1 image: {} - containerPort: 3000 + # -- Container port. 3003 = auth entrypoint inside ghcr.io/ente-io/web. + containerPort: 3003 resources: {} env: [] envFrom: [] @@ -601,7 +620,8 @@ web: replicaCount: 1 image: {} - containerPort: 3000 + # -- Container port. 3001 = accounts entrypoint inside ghcr.io/ente-io/web. + containerPort: 3001 resources: {} env: [] envFrom: [] @@ -644,7 +664,8 @@ web: replicaCount: 1 image: {} - containerPort: 3000 + # -- Container port. 3004 = cast entrypoint inside ghcr.io/ente-io/web. + containerPort: 3004 resources: {} env: [] envFrom: [] @@ -678,16 +699,69 @@ web: tls: [] # --------------------------------------------------------------------------- - # Share Web App + # Albums Web App (public album sharing — what `museum.config.apps.publicAlbums` points at) # --------------------------------------------------------------------------- - share: - # -- Enable Share web app (public sharing) + # The ghcr.io/ente-io/web image bundles every Ente frontend on a different + # internal port. Public album sharing is served on port 3002. When a user + # generates a public album link, the photos web app produces a URL like + # https://albums.example.com/?t=TOKEN — that domain MUST route to this + # deployment for the album to render. Pointing it anywhere else lands on a + # login screen (which is what the photos app, port 3000, shows by default). + albums: + # -- Enable Albums web app enabled: true # -- Number of replicas replicaCount: 1 image: {} - containerPort: 3000 + # -- Container port. 3002 = albums entrypoint inside ghcr.io/ente-io/web. + containerPort: 3002 + resources: {} + env: [] + envFrom: [] + extraVolumeMounts: [] + extraVolumes: [] + securityContext: {} + podSecurityContext: {} + podAnnotations: {} + podLabels: {} + nodeSelector: {} + tolerations: [] + affinity: {} + + config: + extra: {} + + service: + type: ClusterIP + port: 80 + annotations: {} + + ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: albums.ente.local + paths: + - path: / + pathType: Prefix + tls: [] + + # --------------------------------------------------------------------------- + # Share Web App (public file/locker sharing — `museum.config.apps.publicLocker`) + # --------------------------------------------------------------------------- + # NOT to be confused with public album sharing — this serves the locker app + # (port 3005) used for file shares from Ente's mobile/desktop client. + share: + # -- Enable Share web app (public file/locker sharing) + enabled: false + # -- Number of replicas + replicaCount: 1 + + image: {} + # -- Container port. 3005 = share/locker entrypoint inside ghcr.io/ente-io/web. + containerPort: 3005 resources: {} env: [] envFrom: [] diff --git a/charts/stalwart-mail-ha/Chart.yaml b/charts/stalwart-mail-ha/Chart.yaml index fe50b9e..7b5c212 100644 --- a/charts/stalwart-mail-ha/Chart.yaml +++ b/charts/stalwart-mail-ha/Chart.yaml @@ -2,9 +2,9 @@ apiVersion: v2 name: stalwart-mail-ha description: Helm chart for Stalwart Mail Server (SMTP, IMAP, JMAP, CalDAV/CardDAV) running multi-replica with all state on external backends type: application -version: 0.1.1 +version: 0.1.2 appVersion: "0.16.3" -icon: https://stalw.art/img/logo-stalwart.svg +icon: https://raw.githubusercontent.com/stalwartlabs/stalwart/main/img/logo-red.svg home: https://github.com/l4gdev/helm-charts/tree/main/charts/stalwart-mail-ha sources: - https://github.com/stalwartlabs/stalwart @@ -25,7 +25,7 @@ maintainers: email: contact@l4g.dev kubeVersion: ">=1.23.0-0" annotations: - artifacthub.io/category: communication + artifacthub.io/category: networking artifacthub.io/license: AGPL-3.0 artifacthub.io/links: | - name: Chart Source @@ -38,6 +38,8 @@ annotations: artifacthub.io/prerelease: "false" artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: | + - kind: fixed + description: Replaced 404 stalw.art logo URL with raw GitHub logo-red.svg and switched category from invalid "communication" to "networking" so ArtifactHub indexing succeeds - kind: fixed description: "config.json now uses Stalwart 0.16's `EnvironmentVariable`/`variableName` schema instead of legacy `EnvVar`/`envVar`" - kind: fixed From e340cc57ff26a1b335460f1d483833f225c7732b Mon Sep 17 00:00:00 2001 From: Arkadiusz Surma Date: Mon, 4 May 2026 12:49:15 +0200 Subject: [PATCH 2/2] ci: modernise lint-test workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous workflow pinned Python 3.7, which has been EOL since June 2023 and was removed from GitHub-hosted runners — every run failed at the setup-python step before reaching ct lint. Bumps: - actions/checkout@v2 → @v4 - actions/setup-python@v2 (3.7) → @v5 (3.12) - azure/setup-helm@v1 (3.8.1) → @v4 (3.16.3) - helm/chart-testing-action@v2.2.1 → @v2.6.1 - replaces deprecated ::set-output with $GITHUB_OUTPUT Drops the ct install step. Charts in this repo (ente-photos, freescout, chibisafe) require external Postgres / S3 / etc. that is not provisioned in CI; ct install would crashloop the museum pod and fail. Lint coverage stays in place. --- .github/workflows/lint-test.yaml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml index e2b46fd..ac593d9 100644 --- a/.github/workflows/lint-test.yaml +++ b/.github/workflows/lint-test.yaml @@ -12,44 +12,36 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Helm - uses: azure/setup-helm@v1 + uses: azure/setup-helm@v4 with: - version: v3.8.1 + version: v3.16.3 - name: Add dependency chart repos run: | helm repo add bitnami https://charts.bitnami.com/bitnami - - uses: actions/setup-python@v2 + - name: Set up Python + uses: actions/setup-python@v5 with: - python-version: 3.7 + python-version: '3.12' - name: Set up chart-testing - uses: helm/chart-testing-action@v2.2.1 + uses: helm/chart-testing-action@v2.6.1 - name: Run chart-testing (list-changed) id: list-changed run: | changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }}) if [[ -n "$changed" ]]; then - echo "::set-output name=changed::true" + echo "changed=true" >> "$GITHUB_OUTPUT" fi - name: Run chart-testing (lint) id: lint run: ct lint --config ct.yaml --target-branch ${{ github.event.repository.default_branch }} --validate-maintainers=false if: steps.list-changed.outputs.changed == 'true' - - - name: Create kind cluster - uses: helm/kind-action@v1.3.0 - if: steps.list-changed.outputs.changed == 'true' - - - name: Run chart-testing (install) - id: install - run: ct install --config ct.yaml --target-branch ${{ github.event.repository.default_branch }} - if: steps.list-changed.outputs.changed == 'true'