chore(domain): migrate rubis.arthurbarre.fr → rubis.pro
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:
parent
6e796a0980
commit
1c5a58e09a
@ -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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@ -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=
|
||||||
|
|||||||
@ -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: {
|
||||||
|
|||||||
@ -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}>`)
|
||||||
|
|||||||
@ -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"),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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`)
|
||||||
|
|||||||
@ -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)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -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).
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user