Files
codexpi/scripts/redeploy.sh
CodexPi 995b17a6e2
Some checks are pending
Qualité / tests (push) Waiting to run
feat: publish CodexPi contest snapshot
2026-11-14 20:23:59 +01:00

121 lines
5.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$BASE"
# The Codex container can mount its default Docker config read-only. Keep build
# metadata in a disposable location so a deployment never fails for that reason.
export DOCKER_CONFIG="${CODEXPI_DOCKER_CONFIG:-/tmp/codexpi-docker-config}"
mkdir -p "$DOCKER_CONFIG"
command -v docker >/dev/null || { echo "Docker est requis." >&2; exit 1; }
if ! docker info >/dev/null 2>&1; then
socket_gid="$(stat -c '%g' /var/run/docker.sock 2>/dev/null || true)"
if [[ -n "$socket_gid" && " $(id -G) " != *" $socket_gid "* ]]; then
echo "Docker n’est pas accessible : le processus courant n’appartient pas au groupe $socket_gid du socket." >&2
echo "Depuis l’hôte, installez le service de boot puis relancez ce script pour recréer l’API avec le bon groupe :" >&2
echo " ./scripts/install-boot-service.sh" >&2
echo " ./scripts/redeploy.sh" >&2
else
echo "Docker n’est pas accessible." >&2
fi
exit 1
fi
docker_gid="$(stat -c '%g' /var/run/docker.sock)"
if docker ps --format '{{.Names}}' | grep -q '^codexpi-safe-redeploy-'; then
echo "Un déploiement CodexPi est déjà en cours ; aucune seconde bascule ne sera lancée."
exit 0
fi
if docker inspect codexpi-api-next >/dev/null 2>&1 || docker inspect codexpi-django-next >/dev/null 2>&1; then
echo "Une version de relève est encore active. Vérifiez-la avant de relancer un déploiement." >&2
exit 1
fi
stamp="$(date +%s)"
image="codexpi-app:deploy-$stamp"
echo "Construction isolée de la nouvelle image CodexPi ($image)…"
docker build --target production -t "$image" "$BASE"
helper="codexpi-safe-redeploy-$stamp"
docker run -d --rm --name "$helper" \
-e CODEXPI_DEPLOY_IMAGE="$image" \
-e DOCKER_GID="$docker_gid" \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$BASE:$BASE" -w "$BASE" \
docker:cli sh -c '
set -eu
api_next=codexpi-api-next
django_next=codexpi-django-next
switched=0
cleanup() {
if [ "$switched" = 0 ]; then
docker rm -f "$api_next" "$django_next" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
wait_healthy() {
container="$1"
attempts=0
while [ "$attempts" -lt 60 ]; do
health=$(docker inspect -f "{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}" "$container" 2>/dev/null || true)
[ "$health" = healthy ] && return 0
attempts=$((attempts + 1))
sleep 2
done
echo "Le conteneur $container n’est pas devenu sain." >&2
return 1
}
wait_drained() {
container="$1"
quiet=0
echo "Drainage de $container : les discussions en cours restent actives…"
# Five consecutive quiet checks are enough to confirm that the old API no
# longer owns a live session, without delaying every deployment by five
# minutes once the last worker has exited.
while [ "$quiet" -lt 5 ]; do
if docker top "$container" -eo pid,args 2>/dev/null | grep -Eq "codexpi\.session_worker|/codex .* exec"; then
quiet=0
else
quiet=$((quiet + 1))
fi
sleep 2
done
}
echo "Démarrage de la nouvelle version en parallèle…"
docker rm -f "$api_next" "$django_next" >/dev/null 2>&1 || true
CODEXPI_IMAGE="$CODEXPI_DEPLOY_IMAGE" docker compose --project-directory "'"$BASE"'" -f "'"$BASE"'/docker-compose.yml" run -d --no-deps --name "$api_next" -e CODEXPI_SKIP_WORKER_RECOVERY=true api >/dev/null
CODEXPI_IMAGE="$CODEXPI_DEPLOY_IMAGE" docker compose --project-directory "'"$BASE"'" -f "'"$BASE"'/docker-compose.yml" run -d --no-deps --name "$django_next" django-web >/dev/null
wait_healthy "$api_next"
wait_healthy "$django_next"
echo "Bascule Nginx vers la nouvelle version saine…"
printf "upstream django_ui { server %s:8001; }\\nupstream fastapi_api { server %s:8000; }\\n" "$django_next" "$api_next" | docker exec -i codexpi-nginx-1 sh -c "cat > /tmp/codexpi-upstreams.conf"
docker exec codexpi-nginx-1 nginx -t
docker exec codexpi-nginx-1 nginx -s reload
switched=1
wait_drained codexpi-api-1
echo "Remplacement de l’ancienne version désormais inactive…"
# Promote only the image which already passed both health checks. The
# stable tag is what systemd/docker compose will reuse after a host reboot.
docker image tag "$CODEXPI_DEPLOY_IMAGE" codexpi-app:local
CODEXPI_IMAGE=codexpi-app:local docker compose --project-directory "'"$BASE"'" -f "'"$BASE"'/docker-compose.yml" up -d --no-deps --force-recreate api django-web
wait_healthy codexpi-api-1
wait_healthy codexpi-django-web-1
echo "Retour vers les services principaux et nettoyage…"
printf "upstream django_ui { server django-web:8001; }\\nupstream fastapi_api { server api:8000; }\\n" | docker exec -i codexpi-nginx-1 sh -c "cat > /tmp/codexpi-upstreams.conf"
docker exec codexpi-nginx-1 nginx -t
docker exec codexpi-nginx-1 nginx -s reload
wait_drained "$api_next"
docker rm -f "$api_next" "$django_next" >/dev/null
switched=0
trap - EXIT
echo "Redéploiement terminé sans coupure."
' >/dev/null
echo "Image construite. Déploiement blue/green programmé ($helper)."
echo "Les anciennes connexions resteront actives jusqu’à la fin de leur discussion."