rubis/.claude/deploy-memory.md
ordinarthur ea539cd1d4
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 55s
Build & Deploy API / build-and-deploy (push) Successful in 1m35s
feat(auth): Google SSO via @adonisjs/ally
Backend
- @adonisjs/ally installé + provider Google configuré (config/ally.ts)
  scopes: userinfo.email + userinfo.profile (non-sensibles, validation
  auto par Google)
- Migration : ajoute google_id (nullable unique) sur users + rend password
  nullable (un user créé via Google n'a pas de mdp en base, il pourra
  l'activer plus tard via "mot de passe oublié")
- AuthGoogleController.redirect : entrée OAuth (le bouton SPA pointe ici)
- AuthGoogleController.callback : matche par google_id puis email,
  crée org+plans+user si nouveau, pose le refresh cookie httpOnly,
  redirige le browser vers le SPA /auth/google/complete?next=...
  (next = / pour user complet, /onboarding/entreprise pour nouveau)
- Routes : GET /api/v1/auth/google/{redirect,callback}
- Env : GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_CALLBACK_URL

Frontend
- Composant GoogleButton réutilisable (full-page redirect, pas fetch —
  OAuth nécessite navigation pour les cookies cross-origin Google)
- AuthDivider "ou" entre SSO et formulaire email/password
- Boutons ajoutés sur /login et /signup
- Route /auth/google/complete : appelle POST /api/v1/auth/refresh (le
  cookie posé par la callback est auto-envoyé), stocke access token +
  user dans authStore, navigue vers `next`. Échec → /login + toast.
- Toast d'erreur sur /login si on revient avec ?google=denied|error|...

K3s
- ConfigMap rubis-api-config : ajout GOOGLE_CALLBACK_URL prod
- Secret rubis-app-secrets : ajout GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET
  (posés via kubectl, pas dans le manifest)

Doc
- .claude/deploy-memory.md mis à jour avec la procédure Google Cloud
  Console (créer OAuth client, redirect URIs, écran de consentement)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 09:24:27 +02:00

8.9 KiB

Deploy memory — rubis

Ce repo contient deux déploiements distincts sur la même infra K3s, namespace rubis :

  1. Landing statique (rubis.arthurbarre.fr) — image nginx-alpine.
  2. App SaaS (app.rubis.arthurbarre.fr) — image AdonisJS + React (V1).

Chacun a sa propre image, son propre Dockerfile, son propre workflow CI.


1. Landing (rubis.arthurbarre.fr)

Infra

  • Deployment : rubis · Container : rubis · NodePort : 30109
  • Image : git.arthurbarre.fr/ordinarthur/rubis
  • Domaine : https://rubis.arthurbarre.fr (sous-domaine temporaire — domaine définitif rubis-sur-l-ongle.fr pas encore acheté)
  • Manifests : k3s/{namespace,deployment,service}.yml
  • Route Traefik : ~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis.yml.j2
  • Workflow CI : .gitea/workflows/deploy.yml
  • Source : landing/ (servi par nginx)

Mise à jour

Push git, le CI build + rollout auto (filter sur landing/**, Dockerfile, k3s/{namespace,deployment,service}.yml).


2. App SaaS (app.rubis.arthurbarre.fr)

Architecture en 2 services : nginx en frontal (web) + API Node interne.

Traefik :443 → app.rubis.arthurbarre.fr:30110 → rubis-web (nginx)
                                                    ├─ /        → SPA static (try_files)
                                                    └─ /api/*   → rubis-api (ClusterIP :3333)

rubis-web — frontend (NodePort 30110, exposé)

  • Image : git.arthurbarre.fr/ordinarthur/rubis-web
  • Container : web (nginx-alpine + SPA Vite dist)
  • Sert /assets/* (cache 1y immutable), / (SPA fallback try_files)
  • Reverse-proxy /api/* → rubis-api.rubis.svc.cluster.local:3333
  • Manifest : k3s/app/web.yml
  • Workflow CI : .gitea/workflows/deploy-web.yml
  • Source : apps/web, packages/shared + apps/web/nginx.conf

rubis-api — backend (ClusterIP, interne uniquement)

  • Image : git.arthurbarre.fr/ordinarthur/rubis-api
  • Container : api (Node 22 + AdonisJS V7)
  • Workers BullMQ dans le même process (cf. start/queue.ts)
  • Init container migrate : node ace.js migration:run --force depuis /app/apps/api/build (idempotent)
  • Probes K3s sur /api/v1/health
  • Manifest : k3s/app/api.yml (Deployment + Service ClusterIP + ConfigMap)
  • Workflow CI : .gitea/workflows/deploy-api.yml
  • Source : apps/api, packages/shared

rubis-redis — backend BullMQ + cache (ClusterIP)

  • Image : redis:7.4-alpine
  • PVC 1Gi local-path, AOF on, maxmemory 256mb allkeys-lru
  • Manifest : k3s/app/redis.yml
  • Re-déployé par le workflow API (path filter inclut redis.yml)

Infra commune

Dépendances externes (déjà déployées)

  • Postgres : 10.10.10.3:5432, base rubis_prod, user rubis
  • MinIO : minio.minio.svc.cluster.local:9000, bucket rubis-prod-invoices, creds = root MinIO
  • Redis : interne au namespace rubis, pas de dépendance externe

Secrets K3s (rubis-app-secrets)

Posés une fois manuellement via kubectl create secret generic (jamais dans les manifests). Re-pose si rotation ou ajout :

kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis create secret generic rubis-app-secrets \
  --from-literal=APP_KEY=... \
  --from-literal=PG_PASSWORD=... \
  --from-literal=S3_ACCESS_KEY=... \
  --from-literal=S3_SECRET_KEY=... \
  --from-literal=RESEND_API_KEY=... \
  --from-literal=MISTRAL_API_KEY=... \
  --from-literal=REDIS_PASSWORD="" \
  --from-literal=GOOGLE_CLIENT_ID=... \
  --from-literal=GOOGLE_CLIENT_SECRET=... \
  --dry-run=client -o yaml | kubectl apply -f -

Google SSO — setup Google Cloud Console

Si la clé OAuth est perdue ou qu'on doit la régénérer :

  1. https://console.cloud.google.com/apis/credentials → projet courant
  2. Create CredentialsOAuth client ID → type Web application
  3. Authorized JavaScript origins :
    • https://app.rubis.arthurbarre.fr
    • http://localhost:5173 (dev SPA)
  4. Authorized redirect URIs :
    • https://app.rubis.arthurbarre.fr/api/v1/auth/google/callback
    • http://localhost:3333/api/v1/auth/google/callback (dev API)
  5. Copier Client ID + Client secret → mettre dans apps/api/.env (dev) et rubis-app-secrets (prod, snippet ci-dessus).

L'écran de consentement OAuth doit être configuré au moins en mode "Testing" avec l'email du user courant ajouté en testeur. Pour la prod (n'importe qui peut se connecter), il faut passer en "In production" (vérification Google si scopes sensibles ; les nôtres userinfo.email + userinfo.profile sont non-sensibles, validation auto).

Mise à jour

Push git → un (ou les deux) workflow(s) CI se déclenchent selon les paths modifiés. Build+rollout indépendants.

Path modifié Workflow déclenché
apps/web/**, Dockerfile.web, k3s/app/web.yml deploy-web.yml
apps/api/**, Dockerfile.api, k3s/app/api.yml, k3s/app/redis.yml deploy-api.yml
packages/shared/**, pnpm-lock.yaml, tsconfig.base.json, … les deux

Particularités du Dockerfile.api

  • node ace build plante avec ERR_UNKNOWN_FILE_EXTENSION car @poppinss/ts-exec ne s'enregistre pas à temps avant l'import de bin/console.ts. Solution : pnpm exec tsx ace.js build --ignore-ts-errors (tsx = esbuild, gère .ts nativement).
  • --ignore-ts-errors car tests/bootstrap.ts référence un type généré tardivement (.adonisjs/client/registry/schema.d.ts). Le typecheck strict est exécuté en CI séparée (pnpm typecheck).

Particularités du Dockerfile.web

  • Vite build appelé directement (pnpm exec vite build) au lieu du script tsc -b && vite build qui plante sans cache .tsbuildinfo à cause de @tanstack/router-core.
  • nginx.conf inclut le upstream rubis-api.rubis.svc.cluster.local:3333. Si on renomme le service API, c'est ici qu'il faut mettre à jour (en plus du manifest).

Voie manuelle (debug, hors CI)

TAG=$(git rev-parse --short HEAD)
# Landing (rubis.arthurbarre.fr)
docker build --platform linux/amd64 -t git.arthurbarre.fr/ordinarthur/rubis:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis set image deploy/rubis rubis=git.arthurbarre.fr/ordinarthur/rubis:$TAG

# App — web (frontend)
docker build --platform linux/amd64 -f Dockerfile.web \
  -t git.arthurbarre.fr/ordinarthur/rubis-web:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis-web:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis set image deploy/rubis-web web=git.arthurbarre.fr/ordinarthur/rubis-web:$TAG

# App — api (backend)
docker build --platform linux/amd64 -f Dockerfile.api \
  -t git.arthurbarre.fr/ordinarthur/rubis-api:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis-api:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
  -n rubis set image deploy/rubis-api api=git.arthurbarre.fr/ordinarthur/rubis-api:$TAG \
  && kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
       -n rubis patch deploy/rubis-api --type=json \
       -p="[{\"op\":\"replace\",\"path\":\"/spec/template/spec/initContainers/0/image\",\"value\":\"git.arthurbarre.fr/ordinarthur/rubis-api:$TAG\"}]"

⚠️ Cross-compile ARM Mac → linux/amd64 plante sur @swc/core au build de l'app. Préférer pousser et laisser le CI Gitea (linux/amd64 natif).

Si changement route/domaine : cd ~/dev/perso/proxmox/ansible && ansible-playbook playbooks/gateway.yml. Penser à sudo systemctl restart traefik sur la gateway si un nouveau domaine ne récupère pas son cert ACME tout seul (le hot-reload de la dynamic config ne déclenche pas toujours le challenge LE).


Quand le domaine définitif sera acheté (rubis-sur-l-ongle.fr)

  1. Records DNS chez OVH : A @, A app, MX/SPF/DKIM pour Resend
  2. Modifier les rules dans rubis.yml.j2 et rubis-app.yml.j2 (Host)
  3. Replay gateway.yml
  4. Maj MAIL_FROM_ADDRESS=relances@rubis-sur-l-ongle.fr dans le secret
  5. (Optionnel) supprimer les A records rubis.arthurbarre.fr et app.rubis.arthurbarre.fr chez OVH

Déjà fait — NE PAS refaire

  • Dockerfile (landing) + Dockerfile.web + Dockerfile.api (app split)
  • nginx.conf (apps/web/nginx.conf) avec upstream rubis-api
  • Manifests k3s/ (landing) + k3s/app/{api,web,redis}.yml
  • Routes Traefik rubis.yml.j2 + rubis-app.yml.j2
  • DNS OVH : A records rubis (id 5413044152) + app.rubis (id 5413305619)
  • Repo Gitea + secrets CI (KUBECONFIG, REGISTRY_PASSWORD)
  • Namespace + secret registry K3s (gitea-registry)
  • Postgres : base rubis_prod + user rubis (10.10.10.3)
  • MinIO : bucket rubis-prod-invoices
  • Secret K3s rubis-app-secrets (APP_KEY, DB pwd, MinIO, Resend, Mistral)
  • ConfigMap rubis-api-config (env non-sensibles)

Les prochains /deploy font uniquement build + rollout via push git.