From cc047013c63d567847848025ffa793448e3adfbc Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Thu, 7 May 2026 02:50:26 +0200 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20init=20container=20migrate=20uti?= =?UTF-8?q?lise=20build/ace.js=20(compil=C3=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le pod plantait en Init:CrashLoopBackOff parce que le init container tournait depuis /app/apps/api avec \`node ace\` — qui charge le shim ace.js → bin/console.ts (TS source). Sans devDeps en runtime, pas de loader TS → ERR_UNKNOWN_FILE_EXTENSION. Fix : workingDir /app/apps/api/build + command \`node ace.js migration:run --force\`. build/ contient les .js compilés. Memory mise à jour pour documenter ce piège. Co-Authored-By: Claude Opus 4.7 --- .claude/deploy-memory.md | 161 ++++++++++++++++++++++++++++++--------- k3s/app/deployment.yml | 7 +- 2 files changed, 129 insertions(+), 39 deletions(-) diff --git a/.claude/deploy-memory.md b/.claude/deploy-memory.md index bf47dd5..a3f55bf 100644 --- a/.claude/deploy-memory.md +++ b/.claude/deploy-memory.md @@ -1,52 +1,139 @@ # Deploy memory — rubis -## Infra -- Namespace : `rubis` -- Deployment : `rubis` -- Container : `rubis` -- NodePort : `30109` +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 pas encore acheté) -- Manifests : `k3s/` (namespace.yml, deployment.yml, service.yml) +- 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` -- Repo Gitea : https://git.arthurbarre.fr/ordinarthur/rubis (remote `gitea`) +- Workflow CI : `.gitea/workflows/deploy.yml` +- Source : `landing/` (servi par nginx) -## Mise à jour (à suivre pour les prochains /deploy) +### Mise à jour +Push git, le CI build + rollout auto (filter sur `landing/**`, `Dockerfile`, +`k3s/{namespace,deployment,service}.yml`). -1. **Voie normale — push git, le CI build + rollout auto** : - ```bash - git push gitea main - ``` +--- -2. **Voie manuelle** (si Docker Desktop tourne sur le Mac) : - ```bash - TAG=$(git rev-parse --short HEAD) - 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 - kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \ - -n rubis rollout status deploy/rubis - ``` +## 2. App SaaS (`app.rubis.arthurbarre.fr`) -3. Si changement route/domaine : `cd ~/dev/perso/proxmox/ansible && ansible-playbook playbooks/gateway.yml` +### Infra +- Deployment : `rubis-app` · Container : `app` · NodePort : `30110` +- Init container `migrate` : `node ace.js migration:run --force` depuis + `/app/apps/api/build` (idempotent) +- Sidecar Redis : Deployment `rubis-redis` (ClusterIP, PVC 1Gi local-path, + backend BullMQ + cache) +- Image : `git.arthurbarre.fr/ordinarthur/rubis-app` +- Domaine : https://app.rubis.arthurbarre.fr +- Manifests : `k3s/app/{deployment.yml,service.yml,redis.yml}` +- Route Traefik : `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis-app.yml.j2` +- Workflow CI : `.gitea/workflows/deploy-app.yml` +- Source : `apps/api`, `apps/web`, `packages/shared` (monorepo pnpm) -4. Vérif : `curl -I https://rubis.arthurbarre.fr` +### 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 -## Quand le domaine définitif sera acheté +### 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="" \ + --dry-run=client -o yaml | kubectl apply -f - +``` -1. Créer le record A chez OVH (ou autre registrar) : `` → `51.38.62.199` -2. Modifier la rule du fichier `~/dev/perso/proxmox/ansible/roles/traefik/templates/rubis.yml.j2` : - `Host(\`\`)` -3. Relancer le playbook `gateway.yml` -4. (Optionnel) supprimer le record DNS `rubis.arthurbarre.fr` si plus utilisé +### Mise à jour +Push git, le CI build l'image (`Dockerfile.app`, multi-stage), apply les +manifests `k3s/app/`, set image, rollout auto. Filter sur `apps/**`, +`packages/**`, `Dockerfile.app`, `k3s/app/**`. -## Stack -Landing statique, servie par **nginx:1.27-alpine** (Dockerfile à la racine, sert `public/`). -Le rebuild est rapide (pas de bundler). +### Particularités du Dockerfile.app +- `node ace build` plante en CI avec `ERR_UNKNOWN_FILE_EXTENSION` car + `@poppinss/ts-exec` ne s'enregistre pas à temps avant l'import de + `bin/console.ts`. **Solution** : on appelle ace via `tsx` (esbuild), + qui gère nativement les `.ts` → `pnpm exec tsx ace.js build --ignore-ts-errors` +- `--ignore-ts-errors` car `tests/bootstrap.ts` référence un type généré + dans `.adonisjs/client/registry/schema.d.ts` qui arrive trop tard. Le + typecheck strict est exécuté côté CI séparément (`pnpm typecheck`). +- Vite build appelé directement (`pnpm exec vite build`) au lieu de + `pnpm build` qui fait `tsc -b && vite build` — le `tsc -b` plante sans + cache `.tsbuildinfo` à cause de @tanstack/router-core. +- SPA dist (`apps/web/dist`) copié dans `apps/api/build/public/` pour + être servi par le static middleware AdonisJS. Une route wildcard + (`start/routes.ts`) sert `index.html` pour les chemins non-API → SPA + routing TanStack Router. + +--- + +## Voie manuelle (debug, hors CI) + +```bash +TAG=$(git rev-parse --short HEAD) +# Landing +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 +docker build --platform linux/amd64 -f Dockerfile.app -t git.arthurbarre.fr/ordinarthur/rubis-app:$TAG . +docker push git.arthurbarre.fr/ordinarthur/rubis-app:$TAG +kubectl --kubeconfig ~/dev/perso/proxmox/k3s/kubeconfig.yaml \ + -n rubis set image deploy/rubis-app app=git.arthurbarre.fr/ordinarthur/rubis-app:$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, manifests K3s, route Traefik (`rubis.yml.j2` + tâche dans `tasks/main.yml`), -DNS OVH (A record `rubis.arthurbarre.fr` id 5413044152), repo Gitea + secrets CI -(`KUBECONFIG`, `REGISTRY_PASSWORD`), namespace + secret registry K3s. +- Dockerfile (landing) + Dockerfile.app (app) +- Manifests `k3s/` (landing) + `k3s/app/` (app + Redis) +- 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) + Les prochains `/deploy` font uniquement build + rollout via push git. diff --git a/k3s/app/deployment.yml b/k3s/app/deployment.yml index dd5ada3..9e25e59 100644 --- a/k3s/app/deployment.yml +++ b/k3s/app/deployment.yml @@ -30,8 +30,11 @@ spec: initContainers: - name: migrate image: git.arthurbarre.fr/ordinarthur/rubis-app:latest - workingDir: /app/apps/api - command: ['node', 'ace', 'migration:run', '--force'] + # On exécute ace depuis build/ (compilé JS) — le shim ace.js de + # /app/apps/api/ charge bin/console.ts (TS) qui n'a pas de loader + # disponible en runtime sans devDeps. + workingDir: /app/apps/api/build + command: ['node', 'ace.js', 'migration:run', '--force'] envFrom: - secretRef: { name: rubis-app-secrets } - configMapRef: { name: rubis-app-config }