chore(devops): hooks Git versionnes + scripts deploy et restore (Cr 4.f.2 / 7.b.2) #75
5 changed files with 262 additions and 0 deletions
53
.githooks/commit-msg
Executable file
53
.githooks/commit-msg
Executable 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
41
.githooks/pre-commit
Executable 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
|
||||
82
docker/cron/scripts/restore-db.sh
Normal file
82
docker/cron/scripts/restore-db.sh
Normal 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
61
scripts/deploy.sh
Executable 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
25
scripts/install-hooks.sh
Executable 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' ' ')"
|
||||
Loading…
Add table
Reference in a new issue