From ce52d2a7335b68d118d5b572f8d31c42e5f1417f Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 9 Aug 2026 13:36:34 +0200 Subject: [PATCH 1/3] Corriger l-execution des scripts shell dans les conteneurs Docker: le checkout Windows (core.autocrlf) convertissait les fins de ligne en CRLF, cassant le shebang des scripts shell dans les images Linux. Imposer eol=lf pour *.sh. --- .gitattributes | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2840beb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto + +# Les scripts shell doivent rester en LF : le CRLF casse le shebang dans une +# image Linux (conteneur « .../entrypoint.sh: not found »). +*.sh text eol=lf \ No newline at end of file From f4e3608d72452195b9cabd4f9e5c1fbfb6f5ea99 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 9 Aug 2026 13:50:50 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Publier=20PostgreSQL=20sur=20un=20port=20h?= =?UTF-8?q?=C3=B4te=20non=20commun?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plusieurs instances PostgreSQL coexistent sur le serveur : publier la base sur 55432 par défaut (configurable via POSTGRES_PORT), sans changer le port interne 5432 utilisé par l'application. --- .env.example | 4 ++++ docker-compose.yml | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index bb2fe02..5241fd7 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,10 @@ # Copier vers .env et renseigner. Ne jamais committer .env. # --- Base de données -------------------------------------------------------- +# Port hôte publié pour PostgreSQL. Non commun à dessein : plusieurs instances +# PostgreSQL coexistent sur le même serveur, et 5432 (voire 5433) est souvent +# déjà pris. Le conteneur app écoute toujours sur 5432 en interne. +POSTGRES_PORT=55432 POSTGRES_USER=planflow # Une valeur par défaut existe dans docker-compose.yml : la base n'étant jamais # publiée ni attachée au réseau du reverse-proxy, ce mot de passe protège d'un diff --git a/docker-compose.yml b/docker-compose.yml index cf2e1ae..cc786da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,10 +34,12 @@ services: # vrai problème. networks: - interne - # Jamais publiée : rien hors de la pile n'a besoin de la base, et un jeu de - # données RH ne doit pas être à une règle de pare-feu du monde entier. - expose: - - '5432' + # Le conteneur reste sur 5432 pour l'application (réseau interne), mais le + # port hôte est publié sur un port non commun, configurable : plusieurs + # instances PostgreSQL coexistent sur le même serveur, et s'en tenir au + # 5432 par défaut les ferait entrer en collision. + ports: + - '${POSTGRES_PORT:-55432}:5432' app: build: From 2c3c67b949bd1317110028f021f4d4f2937c39fd Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 9 Aug 2026 14:22:05 +0200 Subject: [PATCH 3/3] =?UTF-8?q?Provisionner=20le=20r=C3=B4le=20applicatif?= =?UTF-8?q?=20=C3=A0=20chaque=20docker=20compose=20up?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le rôle planflow_app n etait créé qu à la première initialisation du volume : une base existante au rôle absent (ou au mot de passe changé) bloquait l app en échec P1000 sans remède automatique. Ajouter un service db-init, idempotent, qui rejoue docker/init-app-role.sh à chaque up — branche ELSE re-synchronisant le mot de passe — avant que l app ne démarre. --- README.md | 4 +++- docker-compose.yml | 26 ++++++++++++++++++++++++++ docker/init-app-role.sh | 18 +++++++++++++++--- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2e4b206..15ee6dc 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,9 @@ pnpm test:e2e # build, serveur standalone, tests de bout en bout **L'application ne doit pas se connecter en superutilisateur PostgreSQL.** -En docker-compose c'est déjà réglé : `docker/init-app-role.sh` crée au premier démarrage un rôle `planflow_app`, `NOSUPERUSER NOBYPASSRLS`, propriétaire de la base — il lui faut ce droit pour appliquer les migrations, et les politiques sont déclarées en `FORCE` précisément pour s'appliquer aussi au propriétaire. +En docker-compose c'est déjà réglé : d'abord `docker/init-app-role.sh` au premier démarrage, puis le service `db-init` qui le rejoue **à chaque `docker compose up`** — et seulement ensuite l'application. Le rôle `planflow_app`, `NOSUPERUSER NOBYPASSRLS`, propriétaire de la base — il lui faut ce droit pour appliquer les migrations, et les politiques sont déclarées en `FORCE` précisément pour s'appliquer aussi au propriétaire. + +L'idempotence n'est pas un luxe : une base déjà en place dont le rôle manque (ou dont le mot de passe a changé) ne doit pas exiger de SQL à la main. Le service `db-init` corrige les deux cas à chaque relance, et l'application n'est démarrée qu'une fois sa tâche terminée. Le script ne s'exécute qu'à la **première** initialisation du volume. Sur une installation déjà en place, jouer le même SQL à la main puis basculer `DATABASE_URL` sur ce rôle. diff --git a/docker-compose.yml b/docker-compose.yml index cc786da..9d64739 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,30 @@ services: ports: - '${POSTGRES_PORT:-55432}:5432' + # Provisionne le rôle applicatif à chaude occurrence de `up`, pas seulement à + # la première initialisation du volume : inversement, une base existante dont + # le rôle est absent (ou dont le mot de passe a changé) laisserait l'app en + # échec d'authentification P1000 sans recours autre que du SQL à la main. + # Idempotent : rejouable autant de fois que nécessaire. + db-init: + image: postgres:16-alpine + restart: 'no' + depends_on: + db: + condition: service_healthy + environment: + POSTGRES_USER: ${POSTGRES_USER:-planflow} + PGPASSWORD: ${POSTGRES_PASSWORD:-planflow-interne} + POSTGRES_DB: ${POSTGRES_DB:-planflow} + APP_DB_USER: ${APP_DB_USER:-planflow_app} + APP_DB_PASSWORD: ${APP_DB_PASSWORD:-planflow-app-interne} + PGHOST: db + volumes: + - ./docker/init-app-role.sh:/docker-entrypoint-initdb.d/10-init-app-role.sh:ro + command: ['sh', '/docker-entrypoint-initdb.d/10-init-app-role.sh'] + networks: + - interne + app: build: context: . @@ -48,6 +72,8 @@ services: depends_on: db: condition: service_healthy + db-init: + condition: service_completed_successfully environment: NODE_ENV: production # Le compte applicatif, **pas** le superutilisateur d'amorçage : un diff --git a/docker/init-app-role.sh b/docker/init-app-role.sh index c7ec82f..7a67bd5 100755 --- a/docker/init-app-role.sh +++ b/docker/init-app-role.sh @@ -14,18 +14,30 @@ set -eu # ni superutilisateur ni BYPASSRLS. C'est précisément pourquoi les politiques # sont déclarées en FORCE : elles s'appliquent aussi au propriétaire. # -# Ce script ne s'exécute qu'à la **première** initialisation du volume. Pour une -# installation déjà en place, jouer le même SQL à la main (voir README). +# Playé par le conteneur db à la **première** initialisation du volume, puis par +# le service `db-init` à chaque `docker compose up`. Idempotent : la branche +# `ELSE` re-synchronise le mot de passe d'une base déjà en place. APP_ROLE="${APP_DB_USER:-planflow_app}" APP_PASSWORD="${APP_DB_PASSWORD:-planflow-app-interne}" -psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <