Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save florentdestremau/545e0937218cff0f686b969e1bf7ac56 to your computer and use it in GitHub Desktop.

Select an option

Save florentdestremau/545e0937218cff0f686b969e1bf7ac56 to your computer and use it in GitHub Desktop.
worktree-init.sh
#!/usr/bin/env bash
#
# Initialise une stack Docker Compose complète et INDÉPENDANTE pour ce git worktree,
# en parallèle de la stack principale `suf_sf`, puis prépare tout pour lancer la
# suite de tests.
#
# Isolation :
# - Le nom de projet Compose est dérivé du dossier du worktree (ex. `worktree-management`),
# donc réseau / volumes / base de données sont automatiquement séparés de `suf_sf`.
# - Les PORTS publiés sont scannés dynamiquement (port libre suivant) et écrits
# dans `.env.worktree.local` (gitignoré), passé à `docker compose up` via --env-file.
# - Un DOMAINE dédié `suf-<branche>.localhost` est dérivé de la branche :
# * SERVER_NAME (Caddy) -> .env.worktree.local (lu par Docker)
# * SITE_BASE_HOST/SCHEME -> .env.dev.local (lu par Symfony, env dev seulement,
# pour ne pas perturber les tests)
# - La BASE dev est clonée depuis la stack source par copie du volume Docker
# (Postgres source coupé le temps de la copie, puis redémarré).
#
# Usage :
# bin/worktree-init.sh
#
# Variables d'environnement :
# WORKTREE_SEED_DB=0 désactive la copie du volume de la DB dev
# WORKTREE_SOURCE_PROJECT=nom projet Compose source à cloner (défaut: suf_sf)
#
set -euo pipefail
cd "$(dirname "$0")/.."
ENV_OVERRIDE=".env.worktree.local"
# Projet Compose (dérivé du dossier) = préfixe des volumes/conteneurs.
PROJECT="$(docker compose config 2>/dev/null | awk '/^name:/{print $2; exit}')"
# Stack SOURCE d'où l'on clone la base dev (défaut: stack principale `suf_sf`).
SOURCE_PROJECT="${WORKTREE_SOURCE_PROJECT:-suf_sf}"
# Copier le volume de données de la DB dev depuis la source (défaut: oui).
SEED_DB="${WORKTREE_SEED_DB:-1}"
# Domaine dédié dérivé de la branche courante (slug: minuscules, non-alnum -> '-')
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo worktree)"
SLUG="$(printf '%s' "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g')"
WT_HOST="suf-${SLUG}.localhost"
echo "==> Worktree : $(pwd)"
echo "==> Projet Compose : $PROJECT"
echo "==> Domaine : https://$WT_HOST"
# --- Scan de ports libres -----------------------------------------------------
declare -a RESERVED=()
port_in_use() {
local p="$1"
# 1) déjà réservé par ce run 2) écouté sur l'hôte (ss) 3) publié par un conteneur Docker
local r
for r in "${RESERVED[@]:-}"; do [ "$r" = "$p" ] && return 0; done
if command -v ss >/dev/null 2>&1; then
ss -ltnH "sport = :$p" 2>/dev/null | grep -q . && return 0
fi
docker ps --format '{{.Ports}}' 2>/dev/null | grep -qE "(^|[^0-9]):$p->" && return 0
return 1
}
free_port() { # free_port <port_de_base>
local p="$1"
while port_in_use "$p"; do p=$((p + 1)); done
RESERVED+=("$p")
echo "$p"
}
# --- Copie du volume de la DB dev depuis la stack source ----------------------
# Coupe proprement le Postgres source, copie son volume de données au niveau
# fichier (cohérent car la source est arrêtée — bien plus rapide qu'un pg_dump),
# puis le redémarre. Fait AVANT `up` pour que le Postgres du worktree boote
# directement sur les données copiées (pas d'initdb d'un cluster vide).
seed_db_volume() {
local src_vol="${SOURCE_PROJECT}_database_data"
local dst_vol="${PROJECT}_database_data"
local src_ctn="${SOURCE_PROJECT}-database-1"
if ! docker volume inspect "$src_vol" >/dev/null 2>&1; then
echo "==> Copie DB ignorée : volume source '$src_vol' introuvable."
return 0
fi
# Idempotent : si le volume worktree contient déjà un cluster, on n'écrase pas.
if docker run --rm -v "$dst_vol":/d alpine test -f /d/PG_VERSION >/dev/null 2>&1; then
echo "==> Copie DB ignorée : '$dst_vol' déjà initialisé."
return 0
fi
local was_running=0
if [ "$(docker inspect -f '{{.State.Running}}' "$src_ctn" 2>/dev/null)" = "true" ]; then
was_running=1
echo "==> Coupure du Postgres source ($src_ctn) pour copie cohérente…"
docker stop "$src_ctn" >/dev/null
fi
echo "==> Copie du volume $src_vol -> $dst_vol…"
docker run --rm -v "$src_vol":/from:ro -v "$dst_vol":/to alpine \
sh -c 'rm -rf /to/* /to/..?* /to/.[!.]* 2>/dev/null; cp -a /from/. /to/'
if [ "$was_running" = 1 ]; then
echo "==> Redémarrage du Postgres source…"
docker start "$src_ctn" >/dev/null
fi
echo "==> Volume DB copié."
}
# 1. Fichier de ports remappés — recalculé à chaque init (les conteneurs déjà up
# gardent leurs ports ; on ne régénère que si absent pour rester stable).
if [ ! -f "$ENV_OVERRIDE" ]; then
HTTP_PORT=$(free_port 8080)
HTTPS_PORT=$(free_port 8443)
POSTGRES_PORT=$(free_port 5432)
PORT_MYSQL=$(free_port 3306)
REDIS_PORT=$(free_port 6379)
ADMINER_PORT=$(free_port 8090)
MAILPIT_PORT=$(free_port 8030)
MAILPIT_PORT_WEB=$(free_port 8095)
cat > "$ENV_OVERRIDE" <<EOF
# Ports libres scannés + domaine, pour une stack Docker indépendante par worktree.
# Généré par bin/worktree-init.sh — gitignoré via .env.*.local
HTTP_PORT=$HTTP_PORT
HTTPS_PORT=$HTTPS_PORT
HTTP3_PORT=$HTTPS_PORT
POSTGRES_PORT=$POSTGRES_PORT
PORT_MYSQL=$PORT_MYSQL
REDIS_PORT=$REDIS_PORT
ADMINER_PORT=$ADMINER_PORT
MAILPIT_PORT=$MAILPIT_PORT
MAILPIT_PORT_WEB=$MAILPIT_PORT_WEB
SERVER_NAME=$WT_HOST
EOF
echo "==> $ENV_OVERRIDE créé (ports : http=$HTTP_PORT https=$HTTPS_PORT pg=$POSTGRES_PORT redis=$REDIS_PORT)"
else
echo "==> $ENV_OVERRIDE déjà présent (réutilisé)"
fi
# 1b. Host Symfony (génération d'URL en dev) -> .env.dev.local, si pas déjà défini.
# On y met le host:port effectif pour que les liens générés soient cliquables.
HTTPS_EFF="$(awk -F= '/^HTTPS_PORT=/{print $2}' "$ENV_OVERRIDE")"
if ! grep -qs '^SITE_BASE_HOST=' .env.dev.local; then
cat >> .env.dev.local <<EOF
# Worktree $BRANCH — host utilisé par Symfony pour les URL générées (dev only).
# Généré par bin/worktree-init.sh — gitignoré via .env.*.local
SITE_BASE_SCHEME=https
SITE_BASE_HOST=$WT_HOST:$HTTPS_EFF
EOF
echo "==> .env.dev.local : SITE_BASE_HOST=$WT_HOST:$HTTPS_EFF"
fi
# 2. Copie de la base dev depuis la stack source (avant `up`).
if [ "$SEED_DB" = "1" ]; then
seed_db_volume
else
echo "==> Copie DB désactivée (WORKTREE_SEED_DB=0)."
fi
# 3. Démarrage de la stack (le --env-file n'est requis QUE pour `up`, car seul
# `up` publie des ports ; les commandes `exec`/`run` suivantes retrouvent la
# stack par le nom de projet dérivé du dossier).
echo "==> Démarrage des conteneurs…"
docker compose --env-file .env --env-file "$ENV_OVERRIDE" up --detach --wait
# 3. Dépendances PHP
echo "==> composer install…"
docker compose exec -T php composer install --no-interaction --no-progress
# 4. Dépendances JS + build Vite (service `node`, profil tools)
echo "==> yarn install…"
docker compose run --rm node yarn install --frozen-lockfile
echo "==> yarn build (Vite)…"
docker compose run --rm node yarn build
# NB : ni fixtures ni tests ne sont lancés ici — l'init prépare seulement
# l'environnement. Les commandes ci-dessous restent possibles quand tu veux.
cat <<EOF
==> Worktree prêt (stack isolée up, dépendances installées, build Vite fait,
base dev clonée depuis $SOURCE_PROJECT). Ni fixtures ni tests lancés :
App (données dev) : https://$WT_HOST:$HTTPS_EFF
(cert Caddy auto-signé par worktree -> accepter l'avertissement,
ou `make trust-cert` depuis ce worktree — note: même nickname
que la stack principale, donc une seule CA de confiance à la fois)
Toute la suite : make test # base cephee_test à part ; charge les fixtures puis paratest
(Re)charger les fixtures : DB_NAME=cephee_test bash bin/load-fixtures.sh
Un fichier : docker compose exec -e APP_ENV=test php vendor/bin/paratest --no-progress tests/Chemin/MonTest.php
Arrêter la stack : docker compose down
(les volumes DB restent isolés de la stack principale suf_sf)
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment