#!/bin/sh # ============================================================================ # PTA — application automatique des migrations SQL au démarrage de la stack # ============================================================================ # Exécuté par le service jetable `db-migrate` du docker-compose, une fois que # MariaDB est déclaré sain. Le service `php` attend la fin de ce script en # succès : si une migration échoue, l'application ne démarre pas du tout. # # Le schéma initial (database/pta_prod_schema_mariadb.sql) reste joué par # docker-entrypoint-initdb.d à la création du volume. Ce script ne traite que # les fichiers `migration_*.sql`, dans l'ordre alphabétique, sur une base déjà # existante comme sur une base fraîchement créée. # # Un registre `schema_migration` mémorise chaque fichier appliqué avec son # empreinte. Un fichier déjà appliqué et inchangé est ignoré ; un fichier # modifié est rejoué — les migrations PTA sont écrites idempotentes # (CREATE TABLE IF NOT EXISTS, INSERT ... ON DUPLICATE KEY UPDATE, # CREATE OR REPLACE VIEW), les rejouer est sans effet de bord. # ============================================================================ set -eu DB_HOST="${DB_HOST:-db}" DB_PORT="${DB_PORT:-3306}" DB_NAME="${DB_NAME:-pta}" DB_USER="${DB_USER:-root}" MIGRATIONS_DIR="${MIGRATIONS_DIR:-/migrations}" # Le mot de passe passe par MYSQL_PWD et jamais par la ligne de commande : # un `ps` dans le conteneur ne doit pas le révéler. MYSQL_PWD="${DB_PASSWORD:-}" export MYSQL_PWD log() { echo "[pta-migrate] $*" } sql() { # --ssl-verify-server-cert=0 : le client MariaDB 11.4 désactive de toute # façon cette vérification quand le mot de passe ne vient pas de la ligne # de commande, en émettant un avertissement trompeur parlant de « login # sans mot de passe ». Le poser explicitement supprime ce bruit dans les # journaux de déploiement sans rien changer au comportement réel : la # liaison reste interne au réseau bridge du compose. mariadb --host="$DB_HOST" --port="$DB_PORT" --user="$DB_USER" \ --database="$DB_NAME" --default-character-set=utf8mb4 \ --ssl-verify-server-cert=0 "$@" } # ---------------------------------------------------------------------------- # Attente de la base # ---------------------------------------------------------------------------- # Le compose garantit déjà `service_healthy`, mais le healthcheck interroge la # boucle locale du conteneur : cette boucle couvre le court instant où le port # n'accepte pas encore de connexion depuis le réseau. attente=0 until sql -N -B -e 'SELECT 1' >/dev/null 2>&1; do attente=$((attente + 1)) if [ "$attente" -ge 30 ]; then log "ERREUR : base $DB_NAME injoignable sur $DB_HOST:$DB_PORT après 60 s." exit 1 fi log "Base pas encore joignable, nouvelle tentative dans 2 s ($attente/30)…" sleep 2 done log "Base $DB_NAME joignable sur $DB_HOST:$DB_PORT." # ---------------------------------------------------------------------------- # Registre des migrations appliquées # ---------------------------------------------------------------------------- sql <<'SQL' CREATE TABLE IF NOT EXISTS schema_migration ( fichier VARCHAR(190) NOT NULL, empreinte CHAR(32) NOT NULL, applique_le DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (fichier) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; SQL # ---------------------------------------------------------------------------- # Application des migrations # ---------------------------------------------------------------------------- applique=0 ignore=0 for fichier in "$MIGRATIONS_DIR"/migration_*.sql; do # Le glob non résolu se présente tel quel quand aucun fichier ne correspond. [ -f "$fichier" ] || continue nom=$(basename "$fichier") empreinte=$(md5sum < "$fichier" | cut -d' ' -f1) connue=$(sql -N -B -e \ "SELECT empreinte FROM schema_migration WHERE fichier = '$nom'") if [ "$connue" = "$empreinte" ]; then log "$nom : déjà appliquée, ignorée." ignore=$((ignore + 1)) continue fi if [ -n "$connue" ]; then log "$nom : contenu modifié depuis la dernière application, rejeu." else log "$nom : application…" fi if ! sql < "$fichier"; then log "ERREUR : échec de $nom. L'application ne sera pas démarrée." exit 1 fi sql -e "INSERT INTO schema_migration (fichier, empreinte) VALUES ('$nom', '$empreinte') ON DUPLICATE KEY UPDATE empreinte = VALUES(empreinte), applique_le = CURRENT_TIMESTAMP" log "$nom : appliquée." applique=$((applique + 1)) done log "Bilan : $applique migration(s) appliquée(s), $ignore ignorée(s)." # ---------------------------------------------------------------------------- # Contrôle final : les vues de droits doivent exister # ---------------------------------------------------------------------------- # Même contrôle que SchemaModel::assertAccessViews() côté PHP. Le faire ici # évite de laisser démarrer une pile dont la page d'accueil renverrait une # erreur 500 incompréhensible. manquantes=$(sql -N -B -e " SELECT GROUP_CONCAT(attendue.nom ORDER BY attendue.nom SEPARATOR ', ') FROM ( SELECT 'v_acces_role_page' AS nom UNION ALL SELECT 'v_acces_role_permission' UNION ALL SELECT 'v_acces_identite_agent' UNION ALL SELECT 'v_acces_identite_structure' UNION ALL SELECT 'v_acces_agent_par_structure' UNION ALL SELECT 'v_acces_perimetre_creneau' UNION ALL SELECT 'v_acces_perimetre_planning' ) AS attendue LEFT JOIN information_schema.VIEWS v ON v.TABLE_SCHEMA = DATABASE() AND v.TABLE_NAME = attendue.nom WHERE v.TABLE_NAME IS NULL") if [ -n "$manquantes" ] && [ "$manquantes" != "NULL" ]; then log "ERREUR : vue(s) de contrôle d'accès manquante(s) : $manquantes" log "La migration v7.8 n'a pas produit le résultat attendu." exit 1 fi log "Les sept vues de contrôle d'accès sont présentes. Base à jour."