All checks were successful
Build & Deploy Landing / build-and-deploy (push) Successful in 24s
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>
243 lines
11 KiB
Markdown
243 lines
11 KiB
Markdown
# Deploy memory — rubis
|
|
|
|
Ce repo contient **deux déploiements distincts** sur la même infra K3s,
|
|
namespace `rubis` :
|
|
|
|
1. **Landing** statique (`rubis.pro`) — image nginx-alpine.
|
|
2. **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.pro` 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.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 --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
|
|
- 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`, 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 :
|
|
```bash
|
|
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 Credentials** → **OAuth client ID** → type **Web application**
|
|
3. **Authorized JavaScript origins** :
|
|
- `https://app.rubis.pro`
|
|
- `http://localhost:5173` (dev SPA)
|
|
4. **Authorized redirect URIs** :
|
|
- `https://app.rubis.pro/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).
|
|
|
|
### Microsoft SSO — setup Azure / Entra ID
|
|
1. https://portal.azure.com → **Microsoft Entra ID** → **App registrations**
|
|
→ **New registration**
|
|
2. **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)
|
|
3. **Redirect URI** type **Web** :
|
|
- `https://app.rubis.pro/api/v1/auth/microsoft/callback`
|
|
4. Après création : ajouter en plus le redirect dev via **Authentication →
|
|
Add a platform → Web** :
|
|
- `http://localhost:3333/api/v1/auth/microsoft/callback`
|
|
5. **Certificates & secrets → New client secret** : créer, copier la
|
|
*Value* (visible une seule fois) → `MICROSOFT_CLIENT_SECRET`
|
|
6. Page **Overview** → copier *Application (client) ID* → `MICROSOFT_CLIENT_ID`
|
|
7. **API permissions** : `User.Read` (déjà délégué par défaut), pas besoin
|
|
d'admin consent pour les comptes individuels
|
|
8. Mettre les valeurs dans `apps/api/.env` (dev) et `rubis-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 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)
|
|
|
|
```bash
|
|
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 vers `rubis.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 :
|
|
1. Vire les blocs `rubis-legacy-redirect` / `rubis-app-legacy-redirect` (et middlewares) dans `rubis*.yml.j2`
|
|
2. `ansible-playbook playbooks/gateway.yml`
|
|
3. 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` + 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.
|