diff --git a/charts/plane-ce/Chart.yaml b/charts/plane-ce/Chart.yaml index 5d45ab7f..f173b2df 100644 --- a/charts/plane-ce/Chart.yaml +++ b/charts/plane-ce/Chart.yaml @@ -5,7 +5,7 @@ description: Meet Plane. An open-source software development tool to manage issu type: application -version: 1.6.2 +version: 1.6.3 appVersion: "1.4.1" home: https://plane.so diff --git a/charts/plane-ce/README.md b/charts/plane-ce/README.md index c72d5ebf..cdcf366e 100644 --- a/charts/plane-ce/README.md +++ b/charts/plane-ce/README.md @@ -402,8 +402,8 @@ The default value is `"traefik"`. If you previously relied on the implicit defau When `ingress.ingressClass` starts with `traefik`, the chart deploys native Traefik CRDs instead of a standard `Ingress` resource: -- **`IngressRoute`** (`traefik.io/v1alpha1`) — routes traffic to each Plane service via `Host` + `PathPrefix` rules on the `websecure` entrypoint -- **`Middleware`** (`traefik.io/v1alpha1`) — enforces a request body size limit on every route (default 5 MiB, configurable via `ingress.traefik.maxRequestBodyBytes`) +- **`IngressRoute`** (`traefik.io/v1alpha1`) — routes traffic to each Plane service via `Host` + `PathPrefix` rules on the `websecure` entrypoint. Specific paths (`/god-mode`, `/api`, …) use a higher priority than the catch-all `/` route. +- **`Middleware`** (`traefik.io/v1alpha1`) — request body size limit on every route, plus a temporary redirect that adds a trailing slash for `/god-mode`, `/spaces`, and `/live` (required by those SPAs' React Router basenames). This requires the Traefik Helm chart to be installed with `providers.kubernetesCRD.enabled=true` (enabled by default in Traefik v3), as shown in the pre-requisites above. @@ -437,6 +437,7 @@ ssl: | Setting | Default | Required | Description | | -------------------------- | :------------------------------------------------: | :------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| env.web_url | | | Optional public URL for the app (include scheme), e.g. `https://plane.example.com`. When set, used as `WEB_URL` as-is. When empty, `WEB_URL` is derived from `ingress.appHost` using **https** if TLS is configured (`ssl.tls_secret_name`, cert-manager certs, or Traefik ingress), otherwise **http**. | | env.secret_key | 60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5 | Yes | This must a random string which is used for hashing/encrypting the sensitive data within the application. Once set, changing this might impact the already hashed/encrypted data | | env.default_cluster_domain | cluster.local | Yes | Set this value as configured in your kubernetes cluster. `cluster.local` is usally the default in most cases. | diff --git a/charts/plane-ce/templates/config-secrets/app-env.yaml b/charts/plane-ce/templates/config-secrets/app-env.yaml index c15b8e33..1cee649e 100644 --- a/charts/plane-ce/templates/config-secrets/app-env.yaml +++ b/charts/plane-ce/templates/config-secrets/app-env.yaml @@ -52,7 +52,13 @@ data: MINIO_ENDPOINT_SSL: {{ .Values.minio.env.minio_endpoint_ssl | default false | ternary "1" "0" | quote }} API_KEY_RATE_LIMIT: {{ .Values.env.api_key_rate_limit | default "60/minute" | quote }} - {{- if .Values.ingress.appHost }} + {{- /* Prefer explicit env.web_url. Otherwise use https when TLS is configured + (tls_secret_name or cert-manager generated certs), matching plane-enterprise. */}} + {{- if .Values.env.web_url }} + WEB_URL: {{ .Values.env.web_url | quote }} + {{- else if and .Values.ingress.appHost (or .Values.ssl.tls_secret_name (and .Values.ssl.createIssuer .Values.ssl.generateCerts) (hasPrefix "traefik" (.Values.ingress.ingressClass | default ""))) }} + WEB_URL: "https://{{ .Values.ingress.appHost }}" + {{- else if .Values.ingress.appHost }} WEB_URL: "http://{{ .Values.ingress.appHost }}" {{- else }} WEB_URL: "" diff --git a/charts/plane-ce/templates/ingress-traefik.yaml b/charts/plane-ce/templates/ingress-traefik.yaml index 6382f647..97685cd3 100644 --- a/charts/plane-ce/templates/ingress-traefik.yaml +++ b/charts/plane-ce/templates/ingress-traefik.yaml @@ -13,11 +13,16 @@ spec: routes: - # IMPORTANT: specific paths FIRST + {{/* + Explicit priorities: Traefik can otherwise prefer the catch-all PathPrefix(`/`) + over more specific routes depending on rule length / middleware side effects. + */}} - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/spaces`) kind: Rule + priority: 100 middlewares: + - name: {{ .Release.Name }}-trailing-slash - name: {{ .Release.Name }}-body-limit services: - name: {{ .Release.Name }}-space @@ -25,7 +30,9 @@ spec: - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/god-mode`) kind: Rule + priority: 100 middlewares: + - name: {{ .Release.Name }}-trailing-slash - name: {{ .Release.Name }}-body-limit services: - name: {{ .Release.Name }}-admin @@ -33,6 +40,7 @@ spec: - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/api`) kind: Rule + priority: 100 middlewares: - name: {{ .Release.Name }}-body-limit services: @@ -41,6 +49,7 @@ spec: - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/auth`) kind: Rule + priority: 100 middlewares: - name: {{ .Release.Name }}-body-limit services: @@ -49,7 +58,9 @@ spec: - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/live`) kind: Rule + priority: 100 middlewares: + - name: {{ .Release.Name }}-trailing-slash - name: {{ .Release.Name }}-body-limit services: - name: {{ .Release.Name }}-live @@ -58,6 +69,7 @@ spec: {{- if and .Values.minio.local_setup .Values.env.docstore_bucket }} - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/{{ .Values.env.docstore_bucket }}`) kind: Rule + priority: 100 middlewares: - name: {{ .Release.Name }}-body-limit services: @@ -68,6 +80,7 @@ spec: # LAST: catch all - match: Host(`{{ .Values.ingress.appHost }}`) && PathPrefix(`/`) kind: Rule + priority: 1 middlewares: - name: {{ .Release.Name }}-body-limit services: diff --git a/charts/plane-ce/templates/traefik-middleware.yaml b/charts/plane-ce/templates/traefik-middleware.yaml index 191a9ad5..8d74c490 100644 --- a/charts/plane-ce/templates/traefik-middleware.yaml +++ b/charts/plane-ce/templates/traefik-middleware.yaml @@ -9,5 +9,24 @@ metadata: spec: buffering: maxRequestBodyBytes: {{ .Values.ingress.traefik.maxRequestBodyBytes | default 5242880 }} - +--- +{{/* + Admin/space/live SPAs use React Router basenames with a trailing slash + (e.g. /god-mode/). Without a client-visible redirect, /god-mode serves HTML + but the router refuses to match. Use a temporary (302) redirect so browsers + do not permanently cache a bad Location (e.g. with the container port). +*/}} +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: {{ .Release.Name }}-trailing-slash + labels: + {{- include "plane.commonLabels" $ | nindent 4 }} + namespace: {{ .Release.Namespace }} +spec: + redirectRegex: + # Match full request URL; keep host/scheme, add trailing slash. + regex: "^(https://[^/]+)/(god-mode|spaces|live)$" + replacement: "${1}/${2}/" + permanent: false {{- end }} diff --git a/charts/plane-ce/values.yaml b/charts/plane-ce/values.yaml index 87fc8416..72494c72 100644 --- a/charts/plane-ce/values.yaml +++ b/charts/plane-ce/values.yaml @@ -208,6 +208,13 @@ external_secrets: env: + # Public app URL (optional). When set, used as WEB_URL as-is (include scheme). + # Example: "https://plane.example.com" + # If empty, WEB_URL is derived from ingress.appHost: + # https when TLS is configured (ssl.tls_secret_name, cert-manager certs, or Traefik ingress), + # otherwise http. + web_url: "" + # NEXT_PUBLIC_DEPLOY_URL: "" # REDIS remote_redis_url: "" #INCASE OF REMOTE REDIS ONLY