From 6bf32edb17f372624bd0d8a72f01795b00639be6 Mon Sep 17 00:00:00 2001 From: Imugiii Date: Mon, 22 Jun 2026 06:34:18 +0000 Subject: [PATCH] chore(devops): hooks Git versionnes + scripts deploy et restore (Cr 4.f.2 / 7.b.2) --- .githooks/commit-msg | 53 ++++++++++++++++++++ .githooks/pre-commit | 41 ++++++++++++++++ docker/cron/scripts/restore-db.sh | 82 +++++++++++++++++++++++++++++++ scripts/deploy.sh | 61 +++++++++++++++++++++++ scripts/install-hooks.sh | 25 ++++++++++ 5 files changed, 262 insertions(+) create mode 100755 .githooks/commit-msg create mode 100755 .githooks/pre-commit create mode 100644 docker/cron/scripts/restore-db.sh create mode 100755 scripts/deploy.sh create mode 100755 scripts/install-hooks.sh diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..c37cb20 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# Wakdo - hook commit-msg : valide le format Conventional Commits. +# +# Active via scripts/install-hooks.sh (git config core.hooksPath .githooks). +# Recoit en $1 le chemin du fichier contenant le message de commit. +# +# Regle (PROJECT_CONTEXT section 9) : +# (): +# types : feat|fix|refactor|test|docs|chore|ci|db|perf|style +# scope : minuscules, chiffres, tirets +# interdits : emoji (Mantra IA-23) +# +# Exit codes : 0 = message conforme ; 1 = format invalide ou emoji detecte. + +set -euo pipefail + +MSG_FILE="${1:?usage: commit-msg }" + +# Premiere ligne non vide (ignore les commentaires git et les lignes vides). +SUBJECT="$(grep -m1 -vE '^\s*(#|$)' "$MSG_FILE" || true)" + +if [ -z "$SUBJECT" ]; then + echo "commit-msg: message vide." >&2 + exit 1 +fi + +# Tolerance : commits techniques de git (merge/revert/fixup) non concernes. +case "$SUBJECT" in + "Merge "*|"Revert "*|"fixup! "*|"squash! "*) exit 0 ;; +esac + +PATTERN='^(feat|fix|refactor|test|docs|chore|ci|db|perf|style)(\([a-z0-9-]+\))?!?: .{5,}' +if ! printf '%s' "$SUBJECT" | grep -qE "$PATTERN"; then + echo "commit-msg: format invalide." >&2 + echo " attendu : (): =5 car.)>" >&2 + echo " types : feat fix refactor test docs chore ci db perf style" >&2 + echo " recu : $SUBJECT" >&2 + exit 1 +fi + +# Refus des emoji (Mantra IA-23). Plage des symboles/pictogrammes courants. +# grep -P (PCRE) est requis pour les classes \x{...} ; il n'est dispo que sur le +# grep GNU (cible Linux/Alpine du projet). Si -P est absent (BSD/macOS), on saute +# ce controle plutot que de bloquer a tort (le format reste verifie ; la CI fait foi). +if printf 'a' | grep -qP 'a' 2>/dev/null; then + if printf '%s' "$SUBJECT" | grep -qP '[\x{1F000}-\x{1FAFF}\x{2600}-\x{27BF}\x{2190}-\x{21FF}\x{2B00}-\x{2BFF}]'; then + echo "commit-msg: emoji detecte dans le sujet (interdit, Mantra IA-23)." >&2 + exit 1 + fi +fi + +exit 0 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..2fa94a5 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# +# Wakdo - hook pre-commit : garde-fous locaux avant chaque commit. +# +# Active via scripts/install-hooks.sh (git config core.hooksPath .githooks). +# Defense en profondeur cote dev ; la protection de reference reste la CI Forgejo +# (secret-scan, php-lint, static-tests) et la branch protection serveur. +# +# Controles : +# 1. Refuse un commit direct sur main ou dev (PROJECT_CONTEXT regle 18.5). +# 2. Lint PHP (php -l) sur les fichiers .php indexes, si php est disponible. +# +# Exit codes : 0 = OK ; 1 = commit bloque. + +set -euo pipefail + +BRANCH="$(git rev-parse --abbrev-ref HEAD)" +if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "dev" ]; then + echo "pre-commit: commit direct sur '$BRANCH' interdit (regle 18.5)." >&2 + echo " cree une branche : git checkout -b feat/ma-feature" >&2 + exit 1 +fi + +# Lint PHP des fichiers indexes (added/copied/modified), si l'outil est present. +if command -v php >/dev/null 2>&1; then + FAILED=0 + while IFS= read -r file; do + [ -n "$file" ] || continue + [ -f "$file" ] || continue + if ! php -l "$file" >/dev/null 2>&1; then + echo "pre-commit: erreur de syntaxe PHP dans $file" >&2 + php -l "$file" >&2 || true + FAILED=1 + fi + done < <(git diff --cached --name-only --diff-filter=ACM -- '*.php') + if [ "$FAILED" -ne 0 ]; then + exit 1 + fi +fi + +exit 0 diff --git a/docker/cron/scripts/restore-db.sh b/docker/cron/scripts/restore-db.sh new file mode 100644 index 0000000..d960a69 --- /dev/null +++ b/docker/cron/scripts/restore-db.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# +# Wakdo - restauration de la BDD depuis un dump produit par backup-db.sh. +# +# Operation MANUELLE (pas un job cron) : restaurer ecrase les donnees courantes. +# A lancer dans le conteneur disposant du client mysql et du reseau de la BDD, p.ex. +# docker compose run --rm -v "$PWD/var/backups:/backups" wakdo-cron \ +# /scripts/restore-db.sh /backups/wakdo_YYYYMMDD_HHMMSS.sql.gz --force +# +# Variables d'env lues (memes que backup-db.sh) : +# DB_HOST DB_PORT DB_NAME DB_USER DB_PASSWORD +# Note : un dump complet contient des DROP/CREATE TABLE ; le compte utilise doit +# donc avoir les privileges DDL. Le user applicatif (DML seul) ne suffit pas : +# fournir un compte privilegie via DB_USER/DB_PASSWORD pour la restauration. +# +# Usage : +# restore-db.sh [--force] +# Sans --force, le script demande une confirmation interactive. +# +# Exit codes : +# 0 - restauration OK +# 1 - variables env manquantes / mauvais usage / fichier absent +# 2 - restauration mysql a echoue +# 3 - confirmation refusee + +set -euo pipefail + +DUMP_FILE="${1:-}" +FORCE="${2:-}" + +log() { + echo "[restore-db $(date -Iseconds)] $*" >&2 +} + +if [ -z "$DUMP_FILE" ]; then + log "usage: restore-db.sh [--force]" + exit 1 +fi + +if [ ! -f "$DUMP_FILE" ]; then + log "ERROR: fichier de dump introuvable : $DUMP_FILE" + exit 1 +fi + +for var in DB_HOST DB_PORT DB_NAME DB_USER DB_PASSWORD; do + if [ -z "${!var:-}" ]; then + log "ERROR: variable $var vide ou non definie" + exit 1 + fi +done + +# Garde-fou : la restauration ecrase la base cible. Confirmation requise sauf --force. +if [ "$FORCE" != "--force" ]; then + printf 'Restaurer %s dans la base "%s" sur %s:%s ? Les donnees actuelles seront ecrasees. [oui/NON] ' \ + "$DUMP_FILE" "$DB_NAME" "$DB_HOST" "$DB_PORT" >&2 + read -r answer + if [ "$answer" != "oui" ]; then + log "restauration annulee." + exit 3 + fi +fi + +# Decompression a la volee si le dump est gzippe. +reader=(cat "$DUMP_FILE") +case "$DUMP_FILE" in + *.gz) reader=(gzip -dc "$DUMP_FILE") ;; +esac + +log "restauration de ${DB_NAME} depuis ${DUMP_FILE}" +if ! "${reader[@]}" | mysql \ + --host="${DB_HOST}" \ + --port="${DB_PORT}" \ + --user="${DB_USER}" \ + --password="${DB_PASSWORD}" \ + --default-character-set=utf8mb4 \ + "${DB_NAME}"; then + log "ERROR: la restauration mysql a echoue" + exit 2 +fi + +log "restauration OK : ${DB_NAME}" +exit 0 diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..0a7eaa5 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# +# Wakdo - deploiement scripte (declenchement humain). +# +# Strategie CD du projet : le deploiement est volontairement DECLENCHE A LA MAIN +# (solo dev, un seul environnement de prod). Ce script fiabilise l'operation +# (Cr 7.b.2) ; il n'est PAS execute automatiquement par la CI. Un veritable +# deploiement continu (job Forgejo sur push main -> SSH -> ce script) reste a armer +# explicitement avec un secret de connexion, decision laissee a l'exploitant. +# +# A lancer SUR L'HOTE de prod, depuis la racine du depot : +# scripts/deploy.sh [BRANCHE] (defaut : main) +# +# Prerequis : docker-compose.prod.yml present (gitignore, propre a l'hote) et un +# .env de prod renseigne. Le service one-shot wakdo-migrate applique migrations + +# seed (idempotents) avant que l'app ne serve. +# +# Exit codes : 0 = OK ; 1 = prerequis manquant / confirmation refusee. + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +BRANCH="${1:-main}" +REMOTE="${GIT_REMOTE:-origin}" +COMPOSE_FILE="docker-compose.prod.yml" + +if [ ! -f "$COMPOSE_FILE" ]; then + echo "deploy: $COMPOSE_FILE introuvable (fichier de prod, propre a l'hote)." >&2 + exit 1 +fi + +if ! command -v docker >/dev/null 2>&1; then + echo "deploy: docker introuvable sur l'hote." >&2 + exit 1 +fi + +echo "Deploiement Wakdo : branche '$BRANCH' via $COMPOSE_FILE" +printf 'Confirmer le deploiement en production ? [oui/NON] ' +read -r answer +if [ "$answer" != "oui" ]; then + echo "deploy: annule." + exit 1 +fi + +echo "[1/4] mise a jour du code (fast-forward only, remote: $REMOTE)" +git fetch --prune "$REMOTE" "$BRANCH" +git checkout "$BRANCH" +git merge --ff-only "$REMOTE/$BRANCH" + +echo "[2/4] recuperation des images" +docker compose -f "$COMPOSE_FILE" pull + +echo "[3/4] demarrage de la stack (migrate + seed idempotents puis app)" +docker compose -f "$COMPOSE_FILE" up -d + +echo "[4/4] etat des services" +docker compose -f "$COMPOSE_FILE" ps + +echo "Deploiement termine." diff --git a/scripts/install-hooks.sh b/scripts/install-hooks.sh new file mode 100755 index 0000000..963e4ab --- /dev/null +++ b/scripts/install-hooks.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# +# Wakdo - installe les hooks Git versionnes (.githooks). +# +# Pointe core.hooksPath vers .githooks (versionne) au lieu de .git/hooks (local, +# non versionne). A lancer une fois apres le clone : +# scripts/install-hooks.sh +# +# Les hooks (pre-commit, commit-msg) sont alors actifs pour ce clone. + +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +HOOKS_DIR="$ROOT/.githooks" + +if [ ! -d "$HOOKS_DIR" ]; then + echo "install-hooks: $HOOKS_DIR introuvable." >&2 + exit 1 +fi + +chmod +x "$HOOKS_DIR"/* 2>/dev/null || true +git -C "$ROOT" config core.hooksPath .githooks + +echo "Hooks Git installes (core.hooksPath = .githooks)." +echo "Actifs : $(cd "$HOOKS_DIR" && ls | tr '\n' ' ')" -- 2.45.3