chore(devops): hooks Git versionnes + scripts deploy et restore (Cr 4.f.2 / 7.b.2) (#75)
All checks were successful
CI / secret-scan (push) Successful in 8s
CI / php-lint (push) Successful in 22s
CI / static-tests (push) Successful in 45s
CI / js-tests (push) Successful in 23s

This commit is contained in:
Corentin JOGUET 2026-06-22 08:44:45 +02:00
parent a1d4332714
commit 09ac9e5a3f
5 changed files with 262 additions and 0 deletions

53
.githooks/commit-msg Executable file
View file

@ -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) :
# <type>(<scope optionnel>): <description min 5 caracteres>
# 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 <fichier-message>}"
# 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 : <type>(<scope>): <description (>=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

41
.githooks/pre-commit Executable file
View file

@ -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

View file

@ -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 <fichier.sql.gz|fichier.sql> [--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 <fichier.sql.gz|fichier.sql> [--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

61
scripts/deploy.sh Executable file
View file

@ -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."

25
scripts/install-hooks.sh Executable file
View file

@ -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' ' ')"