- Plane upgraded to v1.4.1; Gitea SSO login works at
https://plane.ebruno.fr/(button only exists in ≥1.4.0 frontends — v1.2.0 sign-in page had no gitea option). - App
planeSynced/Healthy at chartplane-cetargetRevision 1.6.1 (see trap below). - Deleted orphaned PVC
data-plane-postgresql-0(nsplane, 41d, pre-CNPG postgres; data already migrated + dumped).
Gitea was upgraded to 1.26.4 while its Postgres (ns gitea, pod gitea-postgresql-0, user/db gitea) kept rows from older Gitea. 1.26 changed two column semantics without a data migration:
oauth2_application.redirect_urisis now a JSON column (xormJSON TEXT). Plane row (id=6) was plain text → 500 on authorize:GetOAuth2ApplicationByClientID: invalid character 'h'. Backstage row (id=4) was[url]unquoted → same latent break. Fixed: wrapped values in proper JSON arrays.oauth2_application.client_secretis validated with bcrypt (ValidateClientSecret→bcrypt.CompareHashAndPassword). Plane row held plaintext (32-hex) → token exchange 400unauthorized_client: invalid client secret→ Plane?error_code=5123&error_message=GITEA_OAUTH_PROVIDER_ERRORloop. Fixed: stored bcrypt hash of the existing secret (value from k8s secretplane-gitea-oauth, nsplane— no secret rotation).
- Healthy 1.26 row for comparison: woodpecker (id=5) has
$2b$10$…+ JSON-array redirect_uris. - If Gitea DB is ever restored/rebuilt, re-apply both fixes.
- Plane chart migrator Job:
plane-api-migrate-{{.Release.Revision}}; ArgoCD helm render pinsRevision=1→ on version bump the completed Job is immutable → sync op fails → api loops onwait_for_migrations. Fix: delete stale Job (one-off kubectl) and changetargetRevision(ArgoCD v3.5.1 memoizes failed syncs per chart revision and never auto-retries the same one). - ArgoCD admin session REST API unusable here (password login disabled);
argocd-serversvc REST is port 80→8080 (probe:POST /api/v1/session401 "no credentials supplied"). - Chart
plane-ce1.6.1 vs 1.6.2: templates are almost identical, buthelm.sh/chart+app.kubernetes.io/versionlabels are rendered from the chart version into every pod/job spec → bumping chart version alone = full rollout + migrator-Job immutability failure with zero functional gain (image is pinned in values). 1.6.2 tried 2026-08-20, reverted; 1.6.1 is the settled state — do not bump the chart version. - Plane v1.4.1
GET /api/instances/(unauthenticated) → nested shape{config: {is_gitea_enabled, …}, instance: {current_version, latest_version, …}}. - Plane CE 1.4.1 API tokens:
X-Api-Keyheader,/api/v1/surface only (/api/users/me/is session-only → 401). Verified:/api/v1/users/me/→ 200.
- Cutover dump
/tmp/opencode/plane-cutover.dumpdeleted 2026-08-20 — schema v1.4.1 is now forward-only (no local rollback path). - Woodpecker CI wiring — done 2026-08-20: token in Vault
secret/data/plane(apiToken) → ExternalSecretplane-api-token(nsci, newkubernetes/ci/dir added toappset-manifests) →verify-planestep in.woodpecker-ci/ci.yaml(token auth + version drift guardPLANE_EXPECTED_VERSION=1.4.1). Remaining: user must add Woodpecker secretplane_api_tokenin the Woodpecker UI (repo settings → secrets) — not headless (Gitea-OAuth login). Also fixed pre-existing invalid YAML invalidate-realmstep (multi-line plain scalar →|literal block; was making the whole pipeline unparseable).
2026-07-23
- Root cause identified: Traefik blocks cross-namespace service references. Services in
nexusandsonarqubenamespaces cannot route tooauth2-proxyin theoauth2-proxynamespace. - Solution: Create a local Envoy proxy Deployment in each app namespace (
nexus,sonarqube) that forwards to the real oauth2-proxy service via kube-dns resolution. This sidesteps Traefik's cross-namespace restriction.
kubernetes/ingress/nexus-ingress.yaml— Usesoauth2-proxy-publiclocal Service; fwd-auth middleware calls/oauth2/start?rd=kubernetes/ingress/sonarqube-ingress.yaml— Updated to useoauth2-proxy-publiclocal Servicekubernetes/ingress/sonarqube-middleware.yaml— Updated fwd-auth to use/oauth2/start?rd=https://sonarqube.ebruno.fr/kubernetes/ingress/nexus-oauth2-ext-svc.yaml— Envoy proxy Deployment + Service + ConfigMapkubernetes/ingress/sonarqube-oauth2-ext-svc.yaml— Envoy proxy Deployment + Service + ConfigMapkubernetes/oauth2-proxy/oauth2-proxy.yaml— Removed duplicate--cookie-secureand--redirect-urlflags
- PactBroker middleware (
kubernetes/ingress/pact-broker-middleware.yaml) still uses/oauth2/auth— update to/oauth2/start - The oauth2-proxy ingress file (
kubernetes/oauth2-proxy/oauth2proxy-ingress.yaml) is unused (no DNS record) - Clean up unused ScaffoldStripprefix middleware files
curl https://nexus.ebruno.fr/oauth2/start→ 302 to Keycloak + CSRF cookie ✅curl https://sonarqube.ebruno.fr/oauth2/start→ 302 to Keycloak + CSRF cookie ✅- Full browser flow untested — pending manual verification
Browser → Traefik (IngressRoute in app namespace) → Envoy Proxy (local, same namespace) → oauth2-proxy (oauth2-proxy namespace, via kube-dns) → Keycloak
Each app namespace gets its own Envoy sidecar:
- Nexus:
nexus-oauth2-ext-svc.yaml - SonarQube:
sonarqube-oauth2-ext-svc.yaml
Traefik only sees a same-namespace Service (oauth2-proxy-public), satisfying its routing constraints.