Files
Momento/scripts/deploy-prod.sh
Antigravity 73a3d206b0
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m41s
CI / Deploy production (on server) (push) Failing after 20s
fix(deploy): sync .env depuis .env.docker pour interpolation docker-compose
Docker Compose lit .env (pas .env.docker) pour interpoler ${POSTGRES_PASSWORD}.
Sans .env, il utilise le fallback 'memento' → auth DB fail.
Fix: grep -v ??? .env.docker > .env à chaque deploy.
2026-06-28 15:28:19 +00:00

252 lines
9.4 KiB
Bash
Executable File
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
# Déploiement prod sur 192.168.1.190 — exécuter SUR LE SERVEUR (runner docker-host).
set -euo pipefail
# Telegram notification function
telegram_notify() {
local status="$1"
local message="$2"
local bot_token="${TELEGRAM_BOT_TOKEN:-}"
local chat_id="${TELEGRAM_CHAT_ID:-}"
if [ -z "$bot_token" ] || [ -z "$chat_id" ]; then
echo "TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID not set — skipping notification"
return 0
fi
local emoji="✅"
if [ "$status" = "failure" ]; then
emoji="❌"
elif [ "$status" = "rollback" ]; then
emoji="🔄"
fi
local commit_msg=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "unknown")
# Escape markdown special characters in commit message
commit_msg=$(echo "$commit_msg" | sed 's/[_*`[\]()~>#+=|{}.!\\-]/\\&/g')
local commit_short="${GIT_COMMIT:0:8}"
curl -s -X POST "https://api.telegram.org/bot${bot_token}/sendMessage" \
-d "chat_id=${chat_id}" \
-d "text=${emoji} Momento Deploy ${status}
Commit: ${commit_short}
Message: ${commit_msg}
Details: ${message}" \
-d "parse_mode=Markdown" > /dev/null 2>&1 || echo "Telegram notification failed"
}
# Rollback functions
rollback_save_image() {
echo "=== Saving current image for rollback ==="
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^memento-memento-note:latest"; then
docker tag memento-memento-note:latest memento-memento-note:rollback || echo "WARN: Failed to tag rollback image"
echo "Rollback image saved: memento-memento-note:rollback"
else
echo "WARN: No current memento-note image found to save"
fi
}
rollback_restore_image() {
echo "=== Rolling back to previous image ==="
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^memento-memento-note:rollback"; then
docker tag memento-memento-note:rollback memento-memento-note:latest || echo "ERROR: Failed to restore rollback image"
docker compose up -d --force-recreate memento-note || echo "ERROR: Failed to restart container"
echo "Rollback completed"
else
echo "ERROR: No rollback image found"
return 1
fi
}
ROOT="${DEPLOY_ROOT:-/opt/memento}"
ARTIFACT_TGZ="${ARTIFACT_TGZ:-}"
EXPECTED_COMMIT="${EXPECTED_COMMIT:-}"
load_env_docker() {
local env_file="$ROOT/.env.docker"
[ -f "$env_file" ] || return 0
# Parse ligne par ligne (ne crashe pas sur une valeur avec quote mal fermée,
# contrairement à `source` qui exécute le fichier comme du bash).
local line key val
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|\#*) continue ;; # vide / commentaire
*=*) ;; # doit contenir =
*) continue ;;
esac
key="${line%%=*}"
val="${line#*=}"
key="${key%%[[:space:]]}" # trim trailing whitespace du key
# skip placeholders et valeurs vides
[ -z "$val" ] && continue
[ "$val" = "???" ] && continue
# strip les guillemets entourants seulement s'ils sont équilibrés
case "$val" in
\"*\") val="${val#\"}"; val="${val%\"}" ;;
\'*\') val="${val#\'}"; val="${val%\'}" ;;
esac
# valide le nom de variable (alnum + underscore, commence par une lettre)
case "$key" in
[A-Za-z_]*|[A-Za-z_]*) export "$key=$val" ;;
esac
done < "$env_file"
}
wait_for_postgres() {
local pg_user="${POSTGRES_USER:-memento}"
local pg_db="${POSTGRES_DB:-memento}"
if docker compose up -d --wait postgres 2>/dev/null; then
echo "Postgres healthy (compose --wait)"
return 0
fi
docker compose up -d postgres
for i in $(seq 1 30); do
# Use credentials from inside the container (authoritative)
if docker compose exec -T postgres sh -c 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >/dev/null 2>&1; then
echo "Postgres ready (${i}/30)"
return 0
fi
# Fallback: host env after load_env_docker
if docker compose exec -T postgres pg_isready -U "$pg_user" -d "$pg_db" >/dev/null 2>&1; then
echo "Postgres ready via host env (${i}/30)"
return 0
fi
sleep 2
done
echo "Postgres not ready after 60s"
docker compose ps postgres 2>/dev/null || true
docker compose logs postgres --tail=50 2>/dev/null || true
docker compose exec -T postgres sh -c 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' 2>&1 || true
return 1
}
# Health check configuration: 24 iterations × 5 seconds = 2 minutes total timeout
# This allows Next.js cold start time while keeping feedback fast
HEALTH_CHECK_MAX_ITERATIONS=24
HEALTH_CHECK_SLEEP_SECONDS=5
cd "$ROOT"
if [ -f "$ROOT/.env.docker" ]; then
sed -i 's/\r$//' "$ROOT/.env.docker"
# Docker Compose reads .env (not .env.docker) for ${...} interpolation.
# Sync .env from .env.docker so POSTGRES_PASSWORD etc. are interpolated correctly.
grep -v '???' "$ROOT/.env.docker" | grep -v '^#' > "$ROOT/.env"
fi
load_env_docker
git config --global --add safe.directory "$ROOT" 2>/dev/null || true
git fetch origin main
git reset --hard origin/main
export GIT_COMMIT="$(git rev-parse HEAD)"
echo "=== Deploy $GIT_COMMIT ==="
if [ -n "$EXPECTED_COMMIT" ] && [ "$GIT_COMMIT" != "$EXPECTED_COMMIT" ]; then
echo "ERROR: git HEAD $GIT_COMMIT != expected $EXPECTED_COMMIT"
exit 1
fi
wait_for_postgres || exit 1
docker compose exec -T postgres sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "CREATE EXTENSION IF NOT EXISTS vector;"' >/dev/null
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^memento-note/prisma/migrations/'; then
DUMP_FILE="/opt/memento/backups/pre-migrate-$(date +%Y%m%d-%H%M%S).sql.gz"
mkdir -p /opt/memento/backups
docker compose exec -T postgres pg_dump -U "${POSTGRES_USER:-memento}" -d "${POSTGRES_DB:-memento}" | gzip > "$DUMP_FILE"
DUMP_SIZE=$(stat -c%s "$DUMP_FILE")
if [ "$DUMP_SIZE" -lt 1048576 ]; then
echo "ERROR: Dump too small ($DUMP_SIZE bytes)"
exit 1
fi
echo "Backup: $DUMP_FILE ($(( DUMP_SIZE / 1024 ))KB)"
else
echo "No migration change — skip pre-migrate dump"
fi
export COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1
export GIT_COMMIT
# Save current image for rollback before building new one
rollback_save_image
if [ -n "$ARTIFACT_TGZ" ] && [ -f "$ARTIFACT_TGZ" ]; then
echo "=== Fast image (CI artifact) ==="
tar xzf "$ARTIFACT_TGZ" -C memento-note/
export MEMENTO_DOCKERFILE=Dockerfile.prebuilt
export MEMENTO_SOCKET_DOCKERFILE=Dockerfile.socket.prebuilt
else
echo "ERROR: No CI artifact at $ARTIFACT_TGZ — full Docker build is not supported on this host."
telegram_notify "failure" "Deploy aborted: missing prebuilt artifact"
exit 1
fi
docker compose build memento-note
docker compose build memento-socket
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^mcp-server/'; then
docker compose build mcp-server
fi
docker compose up -d --remove-orphans --force-recreate memento-note
docker compose up -d memento-socket
docker compose up -d mcp-server 2>/dev/null || true
# Monitoring stack updates are handled at the end of successful deployment
echo "=== Migrations (Prisma CLI via node, pas npx) ==="
if docker compose exec -T memento-note test -f ./node_modules/prisma/build/index.js 2>/dev/null; then
docker compose exec -T memento-note node ./node_modules/prisma/build/index.js migrate deploy
else
echo "WARN: prisma CLI absent de l'image — les migrations tournent au démarrage (entrypoint) ou via psql manuel"
fi
nginx -t 2>/dev/null && systemctl reload nginx 2>/dev/null || true
for i in $(seq 1 "$HEALTH_CHECK_MAX_ITERATIONS"); do
BODY=$(docker compose exec -T memento-note node -e "require('http').get('http://localhost:3000/api/build-info',r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>{console.log(d);process.exit(0)})}).on('error',()=>process.exit(1))" 2>/dev/null || true)
ACTUAL=$(echo "$BODY" | jq -r '.commit // empty' 2>/dev/null || true)
if [ "$ACTUAL" = "$GIT_COMMIT" ]; then
echo "OK build-info commit=$ACTUAL"
echo "=== Updating monitoring stack ==="
# Fix potential Docker directory/file mount corruption for metrics-token
if [ -d monitoring/metrics-token ]; then
echo "WARN: monitoring/metrics-token is a directory on host, removing it"
rm -rf monitoring/metrics-token
git checkout monitoring/metrics-token || echo "default-token-value" > monitoring/metrics-token
fi
load_env_docker
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${TELEGRAM_CHAT_ID:-}" ]; then
echo "=== Starting Monitoring Stack (with Telegram bot) ==="
docker compose -f monitoring/docker-compose.monitoring.yml --profile telegram up -d --remove-orphans 2>&1 || echo "WARN: Failed to bring up monitoring stack"
else
echo "=== Starting Monitoring Stack (without Telegram bot) ==="
docker compose -f monitoring/docker-compose.monitoring.yml up -d --remove-orphans 2>&1 || echo "WARN: Failed to bring up monitoring stack"
fi
# Force recreation of Grafana, Prometheus and Alertmanager to apply config changes and fix mounts
echo "=== Recreating Grafana, Prometheus and Alertmanager containers ==="
docker compose -f monitoring/docker-compose.monitoring.yml up -d --force-recreate grafana prometheus alertmanager 2>&1 || true
docker compose ps
telegram_notify "success" "Deployment successful — app is healthy"
exit 0
fi
sleep "$HEALTH_CHECK_SLEEP_SECONDS"
done
echo "ERROR: build-info=$ACTUAL expected=$GIT_COMMIT"
docker logs memento-web --tail=40
# Rollback on health check failure
rollback_restore_image
telegram_notify "rollback" "Health check failed — rolled back to previous version"
exit 1