Documentation post-migration du setup email : - /docs/tech/backend.md §12.5 : architecture des 2 flux (Resend pour le sortant transactionnel via send.rubis.pro, OVH MX Plan pour l'entrant humain via @ rubis.pro) - /CLAUDE.md : tableau récap email infra + maj domaine principal rubis.pro / app.rubis.pro, suppression de la question ouverte "domaine définitif" (résolue) et "endpoint waitlist" (remplacé par CTA app) - /.claude/deploy-memory.md : section migration rubis.pro marquée ✅ avec checklist décommissionnement legacy - /landing/confidentialite.html : remplace privacy@rubis.pro par contact@rubis.pro (alignement avec les boîtes OVH créées) Adresses opérationnelles : - contact@rubis.pro (général + RGPD) - dev@rubis.pro (notifs techniques) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
11 KiB
Deploy memory — rubis
Ce repo contient deux déploiements distincts sur la même infra K3s,
namespace rubis :
- Landing statique (
rubis.pro) — image nginx-alpine. - App SaaS (
app.rubis.pro) — image AdonisJS + React (V1).
Chacun a sa propre image, son propre Dockerfile, son propre workflow CI.
1. Landing (rubis.pro)
Infra
- Deployment :
rubis· Container :rubis· NodePort :30109 - Image :
git.arthurbarre.fr/ordinarthur/rubis - Domaine : https://rubis.pro (sous-domaine temporaire —
domaine définitif
rubis.propas 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.pro)
Architecture en 2 services : nginx en frontal (web) + API Node interne.
Traefik :443 → app.rubis.pro: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 --forcedepuis/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
- Domaine : https://app.rubis.pro (DNS A 5413305619)
- Route Traefik :
~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis-app.yml.j2
Dépendances externes (déjà déployées)
- Postgres : 10.10.10.3:5432, base
rubis_prod, userrubis - MinIO :
minio.minio.svc.cluster.local:9000, bucketrubis-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 :
- https://console.cloud.google.com/apis/credentials → projet courant
- Create Credentials → OAuth client ID → type Web application
- Authorized JavaScript origins :
https://app.rubis.prohttp://localhost:5173(dev SPA)
- Authorized redirect URIs :
https://app.rubis.pro/api/v1/auth/google/callbackhttp://localhost:3333/api/v1/auth/google/callback(dev API)
- Copier
Client ID+Client secret→ mettre dansapps/api/.env(dev) etrubis-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).
Microsoft SSO — setup Azure / Entra ID
- https://portal.azure.com → Microsoft Entra ID → App registrations → New registration
- Name :
Rubis Sur l'Ongle; Supported account types :- "Accounts in any organizational directory and personal Microsoft accounts" (tenant=common, recommandé)
- ou "Accounts in any organizational directory" (tenant=organizations, M365 strict)
- Redirect URI type Web :
https://app.rubis.pro/api/v1/auth/microsoft/callback
- Après création : ajouter en plus le redirect dev via Authentication →
Add a platform → Web :
http://localhost:3333/api/v1/auth/microsoft/callback
- Certificates & secrets → New client secret : créer, copier la
Value (visible une seule fois) →
MICROSOFT_CLIENT_SECRET - Page Overview → copier Application (client) ID →
MICROSOFT_CLIENT_ID - API permissions :
User.Read(déjà délégué par défaut), pas besoin d'admin consent pour les comptes individuels - Mettre les valeurs dans
apps/api/.env(dev) etrubis-app-secrets(prod).
Le client secret expire (Azure force 6 ou 12 mois max) — penser à le renouveler avant échéance ; sinon les nouvelles connexions échoueront en silence après expiration.
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 buildplante avecERR_UNKNOWN_FILE_EXTENSIONcar@poppinss/ts-execne s'enregistre pas à temps avant l'import debin/console.ts. Solution :pnpm exec tsx ace.js build --ignore-ts-errors(tsx = esbuild, gère .ts nativement).--ignore-ts-errorscartests/bootstrap.tsré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 scripttsc -b && vite buildqui 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.pro)
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).
Domaine actuel — rubis.pro ✅ migré (2026-05-07)
- Landing : https://rubis.pro
- App SaaS : https://app.rubis.pro
- Compat legacy :
rubis.arthurbarre.fr/app.rubis.arthurbarre.fr→ 301 versrubis.pro/app.rubis.pro(Traefik dynamic config dans~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis*.yml.j2)
Email rubis.pro
| Flux | Provider | Setup | Adresses |
|---|---|---|---|
| Sortant (transactionnel) | Resend | DKIM + SPF + DMARC sur send.rubis.pro (verified dashboard Resend) |
relances@rubis.pro (via MAIL_FROM_ADDRESS secret K3s) |
| Entrant (humain) | OVH MX Plan (gratuit) | MX @ → OVH (auto-configuré via Espace Client) |
contact@rubis.pro, dev@rubis.pro |
⚠️ Resend Inbound (toggle "Enable Receiving") doit rester OFF : il veut le MX @, qui est pris par OVH MX Plan.
Décommissionnement progressif arthurbarre.fr (à faire dans 30-90 jours)
Quand confiance acquise et plus aucune référence vivante :
- Vire les blocs
rubis-legacy-redirect/rubis-app-legacy-redirect(et middlewares) dansrubis*.yml.j2 ansible-playbook playbooks/gateway.yml- Supprime A records
rubis(id 5413044152) +app.rubis(id 5413305619) dans la zone DNS OVH d'arthurbarre.fr
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+ userrubis(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.