diff --git a/README.md b/README.md index ea6418d..a02fb0a 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ The namespace must exist first, and so must the AppRole Secret the store reads. | `N8N_IMAGE`, `POSTGRES_IMAGE` | pinned images, tag included | | `N8N_STORAGE`, `POSTGRES_STORAGE` | claim sizes, e.g. `2Gi`, `5Gi` | | `TIMEZONE` | e.g. `Europe/Paris`; drives cron triggers | +| `EXECUTIONS_MAX_AGE_HOURS`, `BLOCK_ENV_ACCESS` | **quote these inside the value**: `'"336"'`, `'"false"'`. They land in a ConfigMap, whose values must be strings, and the template cannot force it — kustomize strips quotes and `!!str` alike before substitution runs | | `INGRESS_CLASS`, `INGRESS_BODY_SIZE` | e.g. `nginx`, `"50m"` | | `N8N_WEBHOOK_AUTH_URL` | an endpoint that answers 2xx for an authorised caller and 401/403 otherwise. **Required** — n8n's trigger endpoints carry no authentication of their own, so without this anyone who can reach the host can run any workflow. An OIDC provider's `userinfo` works as-is | | `TLS_ISSUER_NAME`, `TLS_ISSUER_KIND` | a cert-manager issuer, e.g. `ClusterIssuer` | diff --git a/k8s/n8n/configmap.yaml b/k8s/n8n/configmap.yaml index 2b7eca2..ee09829 100644 --- a/k8s/n8n/configmap.yaml +++ b/k8s/n8n/configmap.yaml @@ -1,7 +1,14 @@ -# Every substituted value is quoted. A ConfigMap's data values must be strings, -# and an unquoted ${VAR} that resolves to a number or a boolean becomes one in -# YAML — the apply then fails on the type, naming a field the operator never -# wrote a type for. +# A ConfigMap's data values must be **strings**, and this file cannot enforce +# that: kustomize re-serialises before Flux substitutes, stripping both quotes +# and an `!!str` tag, so `${VAR}` is always bare by the time a value lands in it. +# A value that then looks like a number or a boolean parses as one and the apply +# fails on the type. +# +# So any value substituted here that is not obviously text must carry its own +# quotes in the substitution map: +# +# EXECUTIONS_MAX_AGE_HOURS: '"336"' +# BLOCK_ENV_ACCESS: '"false"' # # Nothing here is sensitive. The two values that are — the database password and # the credential encryption key — arrive through n8n-secrets. @@ -11,15 +18,15 @@ metadata: name: n8n-config namespace: ${NAMESPACE} data: - GENERIC_TIMEZONE: "${TIMEZONE}" - TZ: "${TIMEZONE}" + GENERIC_TIMEZONE: ${TIMEZONE} + TZ: ${TIMEZONE} NODE_ENV: production # What n8n believes it is reachable at. It goes into the webhook URLs it # generates and into the links it puts in its own UI, so it must be the # ingress host and not the Service — a webhook advertised as `n8n` inside the # cluster is a webhook nothing outside can call. - N8N_HOST: "${N8N_HOSTNAME}" + N8N_HOST: ${N8N_HOSTNAME} N8N_PORT: "5678" N8N_PROTOCOL: https WEBHOOK_URL: https://${N8N_HOSTNAME}/ @@ -33,7 +40,7 @@ data: # The default is to keep every execution for ever, which turns the database # into a log. Two weeks is long enough to debug a failed run. EXECUTIONS_DATA_PRUNE: "true" - EXECUTIONS_DATA_MAX_AGE: "${EXECUTIONS_MAX_AGE_HOURS}" + EXECUTIONS_DATA_MAX_AGE: ${EXECUTIONS_MAX_AGE_HOURS} N8N_DIAGNOSTICS_ENABLED: "false" N8N_VERSION_NOTIFICATIONS_ENABLED: "false" N8N_RUNNERS_ENABLED: "true" @@ -42,4 +49,4 @@ data: # workflow without being stored in the workflow JSON. n8n blocks it by # default, and that default is right wherever more than one team can write a # workflow: any workflow can then read any variable in the pod. - N8N_BLOCK_ENV_ACCESS_IN_NODE: "${BLOCK_ENV_ACCESS}" \ No newline at end of file + N8N_BLOCK_ENV_ACCESS_IN_NODE: ${BLOCK_ENV_ACCESS} \ No newline at end of file