chore(domain): migrate rubis.arthurbarre.fr → rubis.pro
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 26s
Build & Deploy Landing / build-and-deploy (push) Successful in 27s
Build & Deploy API / build-and-deploy (push) Successful in 1m18s

Bascule du domaine principal vers rubis.pro / app.rubis.pro :

- K3s ConfigMaps (api.yml, web.yml) : APP_URL, WEB_URL,
  COOKIE_DOMAIN, OAUTH callbacks pointent vers app.rubis.pro
- Dockerfile.web : ARG VITE_API_URL et VITE_PUBLIC_LANDING_URL
- Workflows Gitea : commentaires + build args web → rubis.pro
- Code API (mail_dispatcher, send_test_email, config/mail) :
  defaults env LANDING_URL et MAIL_FROM_ADDRESS migrés
- Templates env (.env.example) idem
- Docs (architecture, backend, frontend, brand-identity) idem
- AGENTS.md / CLAUDE.md / deploy-memory : pointeurs domaine MAJ

Note : MAIL_FROM_ADDRESS dans le secret K3s reste sur
rubis@arthurbarre.fr tant que le domaine rubis.pro n'est pas
Verified dans Resend. À switcher manuellement après vérif Resend.

Compat : un 301 Traefik redirige rubis.arthurbarre.fr → rubis.pro
(et app.X aussi) — config Ansible dans le repo proxmox.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ordinarthur 2026-05-07 21:32:31 +02:00
parent 6e796a0980
commit 1c5a58e09a
17 changed files with 61 additions and 61 deletions

View File

@ -3,20 +3,20 @@
Ce repo contient **deux déploiements distincts** sur la même infra K3s, Ce repo contient **deux déploiements distincts** sur la même infra K3s,
namespace `rubis` : namespace `rubis` :
1. **Landing** statique (`rubis.arthurbarre.fr`) — image nginx-alpine. 1. **Landing** statique (`rubis.pro`) — image nginx-alpine.
2. **App SaaS** (`app.rubis.arthurbarre.fr`) — image AdonisJS + React (V1). 2. **App SaaS** (`app.rubis.pro`) — image AdonisJS + React (V1).
Chacun a sa propre image, son propre Dockerfile, son propre workflow CI. Chacun a sa propre image, son propre Dockerfile, son propre workflow CI.
--- ---
## 1. Landing (`rubis.arthurbarre.fr`) ## 1. Landing (`rubis.pro`)
### Infra ### Infra
- Deployment : `rubis` · Container : `rubis` · NodePort : `30109` - Deployment : `rubis` · Container : `rubis` · NodePort : `30109`
- Image : `git.arthurbarre.fr/ordinarthur/rubis` - Image : `git.arthurbarre.fr/ordinarthur/rubis`
- Domaine : https://rubis.arthurbarre.fr (sous-domaine **temporaire** - Domaine : https://rubis.pro (sous-domaine **temporaire**
domaine définitif `rubis-sur-l-ongle.fr` pas encore acheté) domaine définitif `rubis.pro` pas encore acheté)
- Manifests : `k3s/{namespace,deployment,service}.yml` - Manifests : `k3s/{namespace,deployment,service}.yml`
- Route Traefik : `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis.yml.j2` - Route Traefik : `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis.yml.j2`
- Workflow CI : `.gitea/workflows/deploy.yml` - Workflow CI : `.gitea/workflows/deploy.yml`
@ -28,12 +28,12 @@ Push git, le CI build + rollout auto (filter sur `landing/**`, `Dockerfile`,
--- ---
## 2. App SaaS (`app.rubis.arthurbarre.fr`) ## 2. App SaaS (`app.rubis.pro`)
**Architecture en 2 services** : nginx en frontal (web) + API Node interne. **Architecture en 2 services** : nginx en frontal (web) + API Node interne.
``` ```
Traefik :443 → app.rubis.arthurbarre.fr:30110 → rubis-web (nginx) Traefik :443 → app.rubis.pro:30110 → rubis-web (nginx)
├─ / → SPA static (try_files) ├─ / → SPA static (try_files)
└─ /api/* → rubis-api (ClusterIP :3333) └─ /api/* → rubis-api (ClusterIP :3333)
``` ```
@ -65,7 +65,7 @@ Traefik :443 → app.rubis.arthurbarre.fr:30110 → rubis-web (nginx)
- Re-déployé par le workflow API (path filter inclut `redis.yml`) - Re-déployé par le workflow API (path filter inclut `redis.yml`)
### Infra commune ### Infra commune
- Domaine : https://app.rubis.arthurbarre.fr (DNS A 5413305619) - Domaine : https://app.rubis.pro (DNS A 5413305619)
- Route Traefik : `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis-app.yml.j2` - Route Traefik : `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis-app.yml.j2`
### Dépendances externes (déjà déployées) ### Dépendances externes (déjà déployées)
@ -97,10 +97,10 @@ 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 1. https://console.cloud.google.com/apis/credentials → projet courant
2. **Create Credentials****OAuth client ID** → type **Web application** 2. **Create Credentials****OAuth client ID** → type **Web application**
3. **Authorized JavaScript origins** : 3. **Authorized JavaScript origins** :
- `https://app.rubis.arthurbarre.fr` - `https://app.rubis.pro`
- `http://localhost:5173` (dev SPA) - `http://localhost:5173` (dev SPA)
4. **Authorized redirect URIs** : 4. **Authorized redirect URIs** :
- `https://app.rubis.arthurbarre.fr/api/v1/auth/google/callback` - `https://app.rubis.pro/api/v1/auth/google/callback`
- `http://localhost:3333/api/v1/auth/google/callback` (dev API) - `http://localhost:3333/api/v1/auth/google/callback` (dev API)
5. Copier `Client ID` + `Client secret` → mettre dans `apps/api/.env` (dev) 5. Copier `Client ID` + `Client secret` → mettre dans `apps/api/.env` (dev)
et `rubis-app-secrets` (prod, snippet ci-dessus). et `rubis-app-secrets` (prod, snippet ci-dessus).
@ -120,7 +120,7 @@ non-sensibles, validation auto).
- ou "Accounts in any organizational directory" (tenant=organizations, - ou "Accounts in any organizational directory" (tenant=organizations,
M365 strict) M365 strict)
3. **Redirect URI** type **Web** : 3. **Redirect URI** type **Web** :
- `https://app.rubis.arthurbarre.fr/api/v1/auth/microsoft/callback` - `https://app.rubis.pro/api/v1/auth/microsoft/callback`
4. Après création : ajouter en plus le redirect dev via **Authentication → 4. Après création : ajouter en plus le redirect dev via **Authentication →
Add a platform → Web** : Add a platform → Web** :
- `http://localhost:3333/api/v1/auth/microsoft/callback` - `http://localhost:3333/api/v1/auth/microsoft/callback`
@ -168,7 +168,7 @@ modifiés. Build+rollout indépendants.
```bash ```bash
TAG=$(git rev-parse --short HEAD) TAG=$(git rev-parse --short HEAD)
# Landing (rubis.arthurbarre.fr) # Landing (rubis.pro)
docker build --platform linux/amd64 -t git.arthurbarre.fr/ordinarthur/rubis:$TAG . docker build --platform linux/amd64 -t git.arthurbarre.fr/ordinarthur/rubis:$TAG .
docker push git.arthurbarre.fr/ordinarthur/rubis:$TAG docker push git.arthurbarre.fr/ordinarthur/rubis:$TAG
kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \ kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \
@ -202,14 +202,14 @@ challenge LE).
--- ---
## Quand le domaine définitif sera acheté (`rubis-sur-l-ongle.fr`) ## Quand le domaine définitif sera acheté (`rubis.pro`)
1. Records DNS chez OVH : A `@`, A `app`, MX/SPF/DKIM pour Resend 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) 2. Modifier les rules dans `rubis.yml.j2` et `rubis-app.yml.j2` (Host)
3. Replay `gateway.yml` 3. Replay `gateway.yml`
4. Maj `MAIL_FROM_ADDRESS=relances@rubis-sur-l-ongle.fr` dans le secret 4. Maj `MAIL_FROM_ADDRESS=relances@rubis.pro` dans le secret
5. (Optionnel) supprimer les A records `rubis.arthurbarre.fr` et 5. (Optionnel) supprimer les A records `rubis.pro` et
`app.rubis.arthurbarre.fr` chez OVH `app.rubis.pro` chez OVH
--- ---

View File

@ -1,6 +1,6 @@
name: Build & Deploy Web name: Build & Deploy Web
# Workflow Web (React/Vite + nginx) — sert app.rubis.arthurbarre.fr. # Workflow Web (React/Vite + nginx) — sert app.rubis.pro.
# Reverse-proxie /api/* vers le service ClusterIP rubis-api. # Reverse-proxie /api/* vers le service ClusterIP rubis-api.
on: on:
push: push:
@ -52,8 +52,8 @@ jobs:
# Vars Vite injectées dans le bundle au build time. Pour staging, # Vars Vite injectées dans le bundle au build time. Pour staging,
# créer un workflow séparé avec d'autres VITE_API_URL. # créer un workflow séparé avec d'autres VITE_API_URL.
build-args: | build-args: |
VITE_API_URL=https://app.rubis.arthurbarre.fr VITE_API_URL=https://app.rubis.pro
VITE_PUBLIC_LANDING_URL=https://rubis.arthurbarre.fr VITE_PUBLIC_LANDING_URL=https://rubis.pro
VITE_USE_MOCKS=false VITE_USE_MOCKS=false
- name: Install kubectl - name: Install kubectl

View File

@ -1,6 +1,6 @@
name: Build & Deploy Landing name: Build & Deploy Landing
# Workflow pour la landing static (rubis.arthurbarre.fr). # Workflow pour la landing static (rubis.pro).
# L'app SaaS (apps/api + apps/web) a son propre workflow : deploy-app.yml. # L'app SaaS (apps/api + apps/web) a son propre workflow : deploy-app.yml.
on: on:
push: push:

View File

@ -151,7 +151,7 @@ Voir `/docs/decisions.md` pour le log complet avec rationale.
## Déploiement ## Déploiement
- **Image** : `git.arthurbarre.fr/ordinarthur/rubis:latest` - **Image** : `git.arthurbarre.fr/ordinarthur/rubis:latest`
- **Domaine actuel** (temporaire) : https://rubis.arthurbarre.fr - **Domaine actuel** (temporaire) : https://rubis.pro
- **Build** : `COPY landing/` → nginx servi sur port 80 - **Build** : `COPY landing/` → nginx servi sur port 80
- Voir `.Codex/deploy-memory.md` pour la procédure complète. - Voir `.Codex/deploy-memory.md` pour la procédure complète.

View File

@ -154,7 +154,7 @@ Voir `/docs/decisions.md` pour le log complet avec rationale.
## Déploiement ## Déploiement
- **Image** : `git.arthurbarre.fr/ordinarthur/rubis:latest` - **Image** : `git.arthurbarre.fr/ordinarthur/rubis:latest`
- **Domaine actuel** (temporaire) : https://rubis.arthurbarre.fr - **Domaine actuel** (temporaire) : https://rubis.pro
- **Build** : `COPY landing/` → nginx servi sur port 80 - **Build** : `COPY landing/` → nginx servi sur port 80
- Voir `.claude/deploy-memory.md` pour la procédure complète. - Voir `.claude/deploy-memory.md` pour la procédure complète.

View File

@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
# ============================================================================= # =============================================================================
# Rubis — image web (SPA React/Vite servi par nginx) # Rubis — image web (SPA React/Vite servi par nginx)
# Sert app.rubis.arthurbarre.fr (front + reverse proxy /api/* → rubis-api). # Sert app.rubis.pro (front + reverse proxy /api/* → rubis-api).
# ============================================================================= # =============================================================================
ARG NODE_VERSION=22.13.1 ARG NODE_VERSION=22.13.1
@ -14,8 +14,8 @@ ARG NGINX_VERSION=1.27-alpine
# `vite build`. Valeurs par défaut = prod ; surcharger via --build-arg pour # `vite build`. Valeurs par défaut = prod ; surcharger via --build-arg pour
# preview/staging. # preview/staging.
# ============================================================================= # =============================================================================
ARG VITE_API_URL=https://app.rubis.arthurbarre.fr ARG VITE_API_URL=https://app.rubis.pro
ARG VITE_PUBLIC_LANDING_URL=https://rubis.arthurbarre.fr ARG VITE_PUBLIC_LANDING_URL=https://rubis.pro
ARG VITE_USE_MOCKS=false ARG VITE_USE_MOCKS=false
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@ -74,7 +74,7 @@ WEB_URL=http://localhost:5173
# Landing publique — lien dans le footer des emails ("Rubis sur l'ongle" # Landing publique — lien dans le footer des emails ("Rubis sur l'ongle"
# pointe vers ce domaine). # pointe vers ce domaine).
#-------------------------------------------------------------------- #--------------------------------------------------------------------
LANDING_URL=https://rubis.arthurbarre.fr LANDING_URL=https://rubis.pro
#-------------------------------------------------------------------- #--------------------------------------------------------------------
# Auth (refresh tokens) # Auth (refresh tokens)
@ -88,7 +88,7 @@ COOKIE_SECURE=false
# Google SSO (Ally) — créer un OAuth Client ID web sur Google Cloud # Google SSO (Ally) — créer un OAuth Client ID web sur Google Cloud
# Console, puis ajouter les redirect URIs : # Console, puis ajouter les redirect URIs :
# - http://localhost:3333/api/v1/auth/google/callback (dev) # - http://localhost:3333/api/v1/auth/google/callback (dev)
# - https://app.rubis.arthurbarre.fr/api/v1/auth/google/callback (prod) # - https://app.rubis.pro/api/v1/auth/google/callback (prod)
#-------------------------------------------------------------------- #--------------------------------------------------------------------
GOOGLE_CLIENT_ID= GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET= GOOGLE_CLIENT_SECRET=
@ -99,7 +99,7 @@ GOOGLE_CALLBACK_URL=http://localhost:3333/api/v1/auth/google/callback
# (Microsoft Entra ID → App registrations → New registration → Web), # (Microsoft Entra ID → App registrations → New registration → Web),
# redirect URIs à enregistrer : # redirect URIs à enregistrer :
# - http://localhost:3333/api/v1/auth/microsoft/callback (dev) # - http://localhost:3333/api/v1/auth/microsoft/callback (dev)
# - https://app.rubis.arthurbarre.fr/api/v1/auth/microsoft/callback (prod) # - https://app.rubis.pro/api/v1/auth/microsoft/callback (prod)
# Tenant : 'common' (work + perso), 'organizations' (M365 only) ou un GUID. # Tenant : 'common' (work + perso), 'organizations' (M365 only) ou un GUID.
#-------------------------------------------------------------------- #--------------------------------------------------------------------
MICROSOFT_CLIENT_ID= MICROSOFT_CLIENT_ID=

View File

@ -100,7 +100,7 @@ export async function sendRelanceEmail({
const subject = renderTemplate(step.subject, vars) const subject = renderTemplate(step.subject, vars)
const body = renderTemplate(step.body, vars) const body = renderTemplate(step.body, vars)
const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis-sur-l-ongle.fr') const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis.pro')
// Le client final connaît l'org (ex: "Arthur Barré"), pas Rubis. On utilise // Le client final connaît l'org (ex: "Arthur Barré"), pas Rubis. On utilise
// le nom de l'org comme display name visible côté client. Fallback : // le nom de l'org comme display name visible côté client. Fallback :
// user.fullName, puis MAIL_FROM_NAME (= "Rubis sur l'ongle") en dernier // user.fullName, puis MAIL_FROM_NAME (= "Rubis sur l'ongle") en dernier
@ -117,7 +117,7 @@ export async function sendRelanceEmail({
nowOrg.startOf('day').diff(invoice.dueDate.startOf('day'), 'days').days nowOrg.startOf('day').diff(invoice.dueDate.startOf('day'), 'days').days
) )
const landingUrl = env.get('LANDING_URL', 'https://rubis.arthurbarre.fr') const landingUrl = env.get('LANDING_URL', 'https://rubis.pro')
// Rendu HTML via React Email — DA Rubis (header rubis-deep + card cream). // Rendu HTML via React Email — DA Rubis (header rubis-deep + card cream).
const htmlBody = await render( const htmlBody = await render(
@ -241,14 +241,14 @@ Ces liens expirent dans 24h.
Merci, Merci,
L'équipe Rubis` L'équipe Rubis`
const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis-sur-l-ongle.fr') const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis.pro')
// Le check-in vient FROM Rubis (notification interne à l'user, pas au // Le check-in vient FROM Rubis (notification interne à l'user, pas au
// client final). On garde donc le brand "Rubis sur l'ongle" comme display, // client final). On garde donc le brand "Rubis sur l'ongle" comme display,
// PAS le nom de l'org. // PAS le nom de l'org.
const fromName = env.get('MAIL_FROM_NAME', "Rubis sur l'ongle") const fromName = env.get('MAIL_FROM_NAME', "Rubis sur l'ongle")
// Rendu HTML — DA Rubis avec 2 boutons CTA Oui/Non. // Rendu HTML — DA Rubis avec 2 boutons CTA Oui/Non.
const landingUrl = env.get('LANDING_URL', 'https://rubis.arthurbarre.fr') const landingUrl = env.get('LANDING_URL', 'https://rubis.pro')
const htmlBody = await render( const htmlBody = await render(
CheckinEmail({ CheckinEmail({
invoice: { invoice: {

View File

@ -45,9 +45,9 @@ export default class SendTestEmail extends BaseCommand {
async run() { async run() {
const driver = env.get('MAIL_DRIVER', 'smtp') const driver = env.get('MAIL_DRIVER', 'smtp')
const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis-sur-l-ongle.fr') const fromAddress = env.get('MAIL_FROM_ADDRESS', 'relances@rubis.pro')
const fromName = env.get('MAIL_FROM_NAME', "Rubis sur l'ongle") const fromName = env.get('MAIL_FROM_NAME', "Rubis sur l'ongle")
const landingUrl = env.get('LANDING_URL', 'https://rubis.arthurbarre.fr') const landingUrl = env.get('LANDING_URL', 'https://rubis.pro')
this.logger.info(`Driver: ${driver}`) this.logger.info(`Driver: ${driver}`)
this.logger.info(`From: ${fromName} <${fromAddress}>`) this.logger.info(`From: ${fromName} <${fromAddress}>`)

View File

@ -6,7 +6,7 @@ const mailConfig = defineConfig({
default: env.get('MAIL_DRIVER', 'smtp'), default: env.get('MAIL_DRIVER', 'smtp'),
from: { from: {
address: env.get('MAIL_FROM_ADDRESS', 'relances@rubis-sur-l-ongle.fr'), address: env.get('MAIL_FROM_ADDRESS', 'relances@rubis.pro'),
name: env.get('MAIL_FROM_NAME', "Rubis sur l'ongle"), name: env.get('MAIL_FROM_NAME', "Rubis sur l'ongle"),
}, },

View File

@ -2,7 +2,7 @@
VITE_API_URL=http://localhost:3333 VITE_API_URL=http://localhost:3333
# URL de la landing publique (lien retour depuis l'app) # URL de la landing publique (lien retour depuis l'app)
VITE_PUBLIC_LANDING_URL=https://rubis.arthurbarre.fr VITE_PUBLIC_LANDING_URL=https://rubis.pro
# Active MSW pour mocker l'API. Laisser à "false" pour taper le vrai backend. # Active MSW pour mocker l'API. Laisser à "false" pour taper le vrai backend.
VITE_USE_MOCKS=false VITE_USE_MOCKS=false

View File

@ -932,7 +932,7 @@
</div> </div>
<div style="margin-left: auto; font-family: var(--body); font-size: 11px; color: var(--ink-3); text-align: right;"> <div style="margin-left: auto; font-family: var(--body); font-size: 11px; color: var(--ink-3); text-align: right;">
Envoyé via <span style="color: var(--rubis); font-weight: 600;">Rubis</span><br> Envoyé via <span style="color: var(--rubis); font-weight: 600;">Rubis</span><br>
rubis-sur-l-ongle.fr rubis.pro
</div> </div>
</div> </div>
</div> </div>
@ -941,7 +941,7 @@
<div class="browser"> <div class="browser">
<div class="bar"> <div class="bar">
<div class="dot"></div><div class="dot"></div><div class="dot"></div> <div class="dot"></div><div class="dot"></div><div class="dot"></div>
<div class="url">rubis-sur-l-ongle.fr</div> <div class="url">rubis.pro</div>
</div> </div>
<div style="padding: 56px 64px 64px; background: var(--cream); position: relative;"> <div style="padding: 56px 64px 64px; background: var(--cream); position: relative;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 64px;"> <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 64px;">
@ -1006,8 +1006,8 @@
<div style="font-family: var(--display); font-weight: 700; font-size: 22px; letter-spacing: -0.02em; color: var(--ink); margin-top: 6px;">Arthur Barré</div> <div style="font-family: var(--display); font-weight: 700; font-size: 22px; letter-spacing: -0.02em; color: var(--ink); margin-top: 6px;">Arthur Barré</div>
<div style="font-size: 12px; color: var(--ink-3); margin-top: 4px;">Fondateur · Rubis Sur l'Ongle</div> <div style="font-size: 12px; color: var(--ink-3); margin-top: 4px;">Fondateur · Rubis Sur l'Ongle</div>
<div style="position: absolute; bottom: 24px; left: 28px; font-size: 11px; color: var(--ink-2); line-height: 1.5;"> <div style="position: absolute; bottom: 24px; left: 28px; font-size: 11px; color: var(--ink-2); line-height: 1.5;">
arthur@rubis-sur-l-ongle.fr<br> arthur@rubis.pro<br>
rubis-sur-l-ongle.fr rubis.pro
</div> </div>
</div> </div>
</div> </div>
@ -1022,7 +1022,7 @@
<div style="font-family: var(--display); font-size: 11px; text-transform: uppercase; letter-spacing: 0.16em; opacity: 0.85;">◆ Rubis</div> <div style="font-family: var(--display); font-size: 11px; text-transform: uppercase; letter-spacing: 0.16em; opacity: 0.85;">◆ Rubis</div>
<div style="font-family: var(--display); font-weight: 700; font-size: 32px; line-height: 1.1; letter-spacing: -0.02em; margin-top: 16px; max-width: 320px;">Arrêtez de courir<br>après votre argent.</div> <div style="font-family: var(--display); font-weight: 700; font-size: 32px; line-height: 1.1; letter-spacing: -0.02em; margin-top: 16px; max-width: 320px;">Arrêtez de courir<br>après votre argent.</div>
</div> </div>
<div style="position: relative; z-index: 1; font-size: 12px; opacity: 0.8;">rubis-sur-l-ongle.fr</div> <div style="position: relative; z-index: 1; font-size: 12px; opacity: 0.8;">rubis.pro</div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -16,8 +16,8 @@ Ce document est la source de vérité technique. Quand le code et ce fichier div
┌─────────────▼──────────────┐ ┌─────────────▼──────────────┐
│ Traefik (Proxmox gateway) │ │ Traefik (Proxmox gateway) │
│ rubis.arthurbarre.fr │ rubis.pro
│ app.rubis-sur-l-ongle.fr │ app.rubis.pro
└─────────────┬──────────────┘ └─────────────┬──────────────┘
┌──────────────────┼──────────────────┐ ┌──────────────────┼──────────────────┐
@ -376,9 +376,9 @@ SPA retry l'appel original avec nouveau token
- Service: minio-external # ExternalName → IP du LXC minio - Service: minio-external # ExternalName → IP du LXC minio
- Secret: rubis-config # DB credentials, MinIO credentials, OCR API key, mail API key - Secret: rubis-config # DB credentials, MinIO credentials, OCR API key, mail API key
- IngressRoute (Traefik) : - IngressRoute (Traefik) :
api.rubis-sur-l-ongle.fr → rubis-api-svc:3333 api.rubis.pro → rubis-api-svc:3333
app.rubis-sur-l-ongle.fr → rubis-web-svc:80 app.rubis.pro → rubis-web-svc:80
rubis-sur-l-ongle.fr → rubis-landing-svc:80 rubis.pro → rubis-landing-svc:80
``` ```
### Pipeline CI Gitea ### Pipeline CI Gitea
@ -419,7 +419,7 @@ healthchecks readinessProbe → service public
- **Chiffrement at-rest** : disque Proxmox chiffré (LUKS) — à confirmer côté infra - **Chiffrement at-rest** : disque Proxmox chiffré (LUKS) — à confirmer côté infra
- **Chiffrement in-transit** : TLS partout (Traefik), connexions PG et MinIO en SSL interne - **Chiffrement in-transit** : TLS partout (Traefik), connexions PG et MinIO en SSL interne
- **Rate limiting** : `@adonisjs/limiter` sur `/auth/*` (5 req/min par IP), routes OCR (10/h par utilisateur) - **Rate limiting** : `@adonisjs/limiter` sur `/auth/*` (5 req/min par IP), routes OCR (10/h par utilisateur)
- **CORS** : whitelist stricte (`app.rubis-sur-l-ongle.fr` uniquement) — refus des origines tierces - **CORS** : whitelist stricte (`app.rubis.pro` uniquement) — refus des origines tierces
- **CSRF** : non-applicable car auth via Bearer header (pas cookie session). Les endpoints email check-in utilisent des tokens signés à TTL court. - **CSRF** : non-applicable car auth via Bearer header (pas cookie session). Les endpoints email check-in utilisent des tokens signés à TTL court.
- **Backups** : - **Backups** :
- PG : dump quotidien dans MinIO (`rubis-backups/pg/<date>.dump`) - PG : dump quotidien dans MinIO (`rubis-backups/pg/<date>.dump`)

View File

@ -1044,7 +1044,7 @@ APP_KEY=<32-chars-random> # cookies + crypto
SESSION_DRIVER=cookie SESSION_DRIVER=cookie
# CORS # CORS
CORS_ORIGINS=https://app.rubis-sur-l-ongle.fr CORS_ORIGINS=https://app.rubis.pro
# DB # DB
DB_CONNECTION=postgres DB_CONNECTION=postgres
@ -1066,7 +1066,7 @@ MINIO_INVOICES_BUCKET=rubis-invoices
MINIO_BACKUPS_BUCKET=rubis-backups MINIO_BACKUPS_BUCKET=rubis-backups
# Mail (Resend) # Mail (Resend)
MAIL_FROM_ADDRESS=relances@rubis-sur-l-ongle.fr MAIL_FROM_ADDRESS=relances@rubis.pro
MAIL_FROM_NAME=Rubis Sur l'Ongle MAIL_FROM_NAME=Rubis Sur l'Ongle
RESEND_API_KEY=<secret> RESEND_API_KEY=<secret>
@ -1077,7 +1077,7 @@ MINDEE_API_KEY=<secret>
# Auth refresh tokens # Auth refresh tokens
REFRESH_TOKEN_TTL_DAYS=30 REFRESH_TOKEN_TTL_DAYS=30
ACCESS_TOKEN_TTL_MINUTES=30 ACCESS_TOKEN_TTL_MINUTES=30
COOKIE_DOMAIN=.rubis-sur-l-ongle.fr COOKIE_DOMAIN=.rubis.pro
COOKIE_SECURE=true COOKIE_SECURE=true
``` ```
@ -1131,7 +1131,7 @@ CMD ["node", "build/bin/server.js"]
- Deployment: rubis-api (port 3333, replicas: 2) - Deployment: rubis-api (port 3333, replicas: 2)
- Deployment: rubis-worker (queue listener, replicas: 1) - Deployment: rubis-worker (queue listener, replicas: 1)
- Service: rubis-api-svc - Service: rubis-api-svc
- IngressRoute Traefik: api.rubis-sur-l-ongle.fr → rubis-api-svc:3333 - IngressRoute Traefik: api.rubis.pro → rubis-api-svc:3333
- Secret: rubis-config (toutes les vars d'env) - Secret: rubis-config (toutes les vars d'env)
``` ```

View File

@ -458,7 +458,7 @@ import { api } from '../../../api/.adonisjs/api' // import des types depuis l'A
export const tuyau = createTuyau({ export const tuyau = createTuyau({
api, api,
baseUrl: import.meta.env.VITE_API_URL, // ex. https://api.rubis-sur-l-ongle.fr baseUrl: import.meta.env.VITE_API_URL, // ex. https://api.rubis.pro
credentials: 'include', // envoie les cookies (refresh token) credentials: 'include', // envoie les cookies (refresh token)
headers: () => ({ headers: () => ({
Authorization: authStore.token ? `Bearer ${authStore.token}` : '', Authorization: authStore.token ? `Bearer ${authStore.token}` : '',
@ -673,14 +673,14 @@ export const formatRelativeDate = (iso: string) => /* "dans 3 jours" */
```bash ```bash
VITE_API_URL=http://localhost:3333 VITE_API_URL=http://localhost:3333
VITE_PUBLIC_LANDING_URL=https://rubis-sur-l-ongle.fr VITE_PUBLIC_LANDING_URL=https://rubis.pro
``` ```
Production via secret K3s injecté dans le build Vite : Production via secret K3s injecté dans le build Vite :
```bash ```bash
VITE_API_URL=https://api.rubis-sur-l-ongle.fr VITE_API_URL=https://api.rubis.pro
VITE_PUBLIC_LANDING_URL=https://rubis-sur-l-ongle.fr VITE_PUBLIC_LANDING_URL=https://rubis.pro
``` ```
Toutes les vars accessibles côté SPA **doivent être préfixées `VITE_`** (sinon Vite ne les expose pas au bundle). Toutes les vars accessibles côté SPA **doivent être préfixées `VITE_`** (sinon Vite ne les expose pas au bundle).

View File

@ -100,11 +100,11 @@ data:
HOST: '0.0.0.0' HOST: '0.0.0.0'
NODE_ENV: 'production' NODE_ENV: 'production'
LOG_LEVEL: 'info' LOG_LEVEL: 'info'
APP_URL: 'https://app.rubis.arthurbarre.fr' APP_URL: 'https://app.rubis.pro'
WEB_URL: 'https://app.rubis.arthurbarre.fr' WEB_URL: 'https://app.rubis.pro'
SESSION_DRIVER: 'cookie' SESSION_DRIVER: 'cookie'
COOKIE_SECURE: 'true' COOKIE_SECURE: 'true'
COOKIE_DOMAIN: 'app.rubis.arthurbarre.fr' COOKIE_DOMAIN: 'app.rubis.pro'
DB_CONNECTION: 'postgres' DB_CONNECTION: 'postgres'
PG_HOST: '10.10.10.3' PG_HOST: '10.10.10.3'
@ -134,11 +134,11 @@ data:
# Google SSO — GOOGLE_CLIENT_ID/SECRET sont dans rubis-app-secrets. # Google SSO — GOOGLE_CLIENT_ID/SECRET sont dans rubis-app-secrets.
# Le callback URL doit matcher EXACTEMENT ce qui est configuré dans # Le callback URL doit matcher EXACTEMENT ce qui est configuré dans
# Google Cloud Console (OAuth Client → Authorized redirect URIs). # Google Cloud Console (OAuth Client → Authorized redirect URIs).
GOOGLE_CALLBACK_URL: 'https://app.rubis.arthurbarre.fr/api/v1/auth/google/callback' GOOGLE_CALLBACK_URL: 'https://app.rubis.pro/api/v1/auth/google/callback'
# Microsoft SSO — MICROSOFT_CLIENT_ID/SECRET sont dans rubis-app-secrets. # Microsoft SSO — MICROSOFT_CLIENT_ID/SECRET sont dans rubis-app-secrets.
# MICROSOFT_TENANT : 'common' (work + perso), 'organizations' (M365 only), # MICROSOFT_TENANT : 'common' (work + perso), 'organizations' (M365 only),
# ou un tenant ID Azure AD spécifique. Le callback URL doit matcher # ou un tenant ID Azure AD spécifique. Le callback URL doit matcher
# EXACTEMENT le redirect URI configuré côté Azure App registration. # EXACTEMENT le redirect URI configuré côté Azure App registration.
MICROSOFT_TENANT: 'common' MICROSOFT_TENANT: 'common'
MICROSOFT_CALLBACK_URL: 'https://app.rubis.arthurbarre.fr/api/v1/auth/microsoft/callback' MICROSOFT_CALLBACK_URL: 'https://app.rubis.pro/api/v1/auth/microsoft/callback'

View File

@ -1,5 +1,5 @@
# Rubis Web — nginx + SPA static + reverse proxy /api/* → rubis-api ClusterIP. # Rubis Web — nginx + SPA static + reverse proxy /api/* → rubis-api ClusterIP.
# Seul service exposé via Traefik (NodePort 30110 → app.rubis.arthurbarre.fr). # Seul service exposé via Traefik (NodePort 30110 → app.rubis.pro).
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment