feat: publish CodexPi contest snapshot
Some checks are pending
Qualité / tests (push) Waiting to run
Some checks are pending
Qualité / tests (push) Waiting to run
This commit is contained in:
120
scripts/redeploy.sh
Normal file
120
scripts/redeploy.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user