Le workflow redémarrait memento-note avec l'ancienne image avant git pull, puis ne forçait pas le remplacement du conteneur après build. On force maintenant --force-recreate après build et on log le commit déployé. Co-authored-by: Cursor <cursoragent@cursor.com>
220 lines
9.5 KiB
YAML
220 lines
9.5 KiB
YAML
name: Deploy to Production
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
deploy:
|
|
name: Build and Deploy
|
|
runs-on: ubuntu-24.04
|
|
needs: [ci]
|
|
steps:
|
|
- name: Setup SSH
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
|
chmod 600 ~/.ssh/id_rsa
|
|
ssh-keyscan -H 192.168.1.190 >> ~/.ssh/known_hosts
|
|
|
|
- name: Update .env.docker from Gitea vars & secrets
|
|
env:
|
|
APP_URL: ${{ vars.APP_URL }}
|
|
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
|
ADMIN_EMAIL: ${{ vars.ADMIN_EMAIL }}
|
|
ALLOW_REGISTRATION: ${{ vars.ALLOW_REGISTRATION }}
|
|
POSTGRES_USER: ${{ vars.POSTGRES_USER }}
|
|
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
|
POSTGRES_DB: ${{ vars.POSTGRES_DB }}
|
|
POSTGRES_PORT: ${{ vars.POSTGRES_PORT }}
|
|
AI_PROVIDER_TAGS: ${{ vars.AI_PROVIDER_TAGS }}
|
|
AI_MODEL_TAGS: ${{ vars.AI_MODEL_TAGS }}
|
|
AI_PROVIDER_EMBEDDING: ${{ vars.AI_PROVIDER_EMBEDDING }}
|
|
AI_MODEL_EMBEDDING: ${{ vars.AI_MODEL_EMBEDDING }}
|
|
AI_PROVIDER_CHAT: ${{ vars.AI_PROVIDER_CHAT }}
|
|
AI_MODEL_CHAT: ${{ vars.AI_MODEL_CHAT }}
|
|
CUSTOM_OPENAI_BASE_URL: ${{ vars.CUSTOM_OPENAI_BASE_URL }}
|
|
CUSTOM_OPENAI_API_KEY: ${{ secrets.CUSTOM_OPENAI_API_KEY }}
|
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
OLLAMA_BASE_URL: ${{ vars.OLLAMA_BASE_URL }}
|
|
EMAIL_PROVIDER: ${{ vars.EMAIL_PROVIDER }}
|
|
SMTP_FROM: ${{ vars.SMTP_FROM }}
|
|
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
|
|
SMTP_HOST: ${{ vars.SMTP_HOST }}
|
|
SMTP_PORT: ${{ vars.SMTP_PORT }}
|
|
SMTP_USER: ${{ vars.SMTP_USER }}
|
|
SMTP_PASS: ${{ secrets.SMTP_PASS }}
|
|
SMTP_SECURE: ${{ vars.SMTP_SECURE }}
|
|
SMTP_IGNORE_CERT: ${{ vars.SMTP_IGNORE_CERT }}
|
|
MCP_MODE: ${{ vars.MCP_MODE }}
|
|
MCP_PORT: ${{ vars.MCP_PORT }}
|
|
WEB_SEARCH_PROVIDER: ${{ vars.WEB_SEARCH_PROVIDER }}
|
|
SEARXNG_URL: ${{ vars.SEARXNG_URL }}
|
|
BRAVE_SEARCH_API_KEY: ${{ secrets.BRAVE_SEARCH_API_KEY }}
|
|
JINA_API_KEY: ${{ secrets.JINA_API_KEY }}
|
|
run: |
|
|
ssh root@192.168.1.190 bash << 'ENDSSH'
|
|
ENV_FILE="/opt/memento/.env.docker"
|
|
touch "$ENV_FILE"
|
|
|
|
upsert() {
|
|
local key="$1" val="$2"
|
|
[ -z "$val" ] && return
|
|
sed -i "/^[[:space:]]*${key}=/d" "$ENV_FILE"
|
|
echo "${key}=\"${val}\"" >> "$ENV_FILE"
|
|
}
|
|
|
|
upsert NEXTAUTH_URL "$APP_URL"
|
|
upsert NEXTAUTH_SECRET "$NEXTAUTH_SECRET"
|
|
upsert ADMIN_EMAIL "$ADMIN_EMAIL"
|
|
upsert ALLOW_REGISTRATION "$ALLOW_REGISTRATION"
|
|
upsert POSTGRES_USER "$POSTGRES_USER"
|
|
upsert POSTGRES_PASSWORD "$POSTGRES_PASSWORD"
|
|
upsert POSTGRES_DB "$POSTGRES_DB"
|
|
upsert POSTGRES_PORT "$POSTGRES_PORT"
|
|
upsert AI_PROVIDER_TAGS "$AI_PROVIDER_TAGS"
|
|
upsert AI_MODEL_TAGS "$AI_MODEL_TAGS"
|
|
upsert AI_PROVIDER_EMBEDDING "$AI_PROVIDER_EMBEDDING"
|
|
upsert AI_MODEL_EMBEDDING "$AI_MODEL_EMBEDDING"
|
|
upsert AI_PROVIDER_CHAT "$AI_PROVIDER_CHAT"
|
|
upsert AI_MODEL_CHAT "$AI_MODEL_CHAT"
|
|
upsert CUSTOM_OPENAI_BASE_URL "$CUSTOM_OPENAI_BASE_URL"
|
|
upsert CUSTOM_OPENAI_API_KEY "$CUSTOM_OPENAI_API_KEY"
|
|
upsert OPENAI_API_KEY "$OPENAI_API_KEY"
|
|
upsert OLLAMA_BASE_URL "$OLLAMA_BASE_URL"
|
|
upsert EMAIL_PROVIDER "$EMAIL_PROVIDER"
|
|
upsert SMTP_FROM "$SMTP_FROM"
|
|
upsert RESEND_API_KEY "$RESEND_API_KEY"
|
|
upsert SMTP_HOST "$SMTP_HOST"
|
|
upsert SMTP_PORT "$SMTP_PORT"
|
|
upsert SMTP_USER "$SMTP_USER"
|
|
upsert SMTP_PASS "$SMTP_PASS"
|
|
upsert SMTP_SECURE "$SMTP_SECURE"
|
|
upsert SMTP_IGNORE_CERT "$SMTP_IGNORE_CERT"
|
|
upsert MCP_MODE "$MCP_MODE"
|
|
upsert MCP_PORT "$MCP_PORT"
|
|
upsert WEB_SEARCH_PROVIDER "$WEB_SEARCH_PROVIDER"
|
|
upsert SEARXNG_URL "$SEARXNG_URL"
|
|
upsert BRAVE_SEARCH_API_KEY "$BRAVE_SEARCH_API_KEY"
|
|
upsert JINA_API_KEY "$JINA_API_KEY"
|
|
|
|
echo ".env.docker updated (container restart after build in deploy step)"
|
|
ENDSSH
|
|
|
|
- name: Tag current image as rollback
|
|
run: |
|
|
ssh root@192.168.1.190 << 'ENDSSH'
|
|
docker tag memento-note_memento-note:latest memento-note_memento-note:rollback 2>/dev/null && echo "Rollback tag saved" || echo "No existing image to tag"
|
|
ENDSSH
|
|
|
|
- name: Deploy via SSH
|
|
run: |
|
|
ssh root@192.168.1.190 << 'ENDSSH'
|
|
set -e
|
|
cd /opt/memento
|
|
|
|
echo "=== Git pull ==="
|
|
git config --global --add safe.directory /opt/memento
|
|
git fetch origin main
|
|
git reset --hard origin/main
|
|
|
|
echo "=== Ensure postgres is running (no recreate — prod data) ==="
|
|
docker compose up -d postgres
|
|
|
|
echo "=== Waiting for postgres healthy ==="
|
|
for i in $(seq 1 30); do
|
|
if docker compose exec -T postgres pg_isready -U "${POSTGRES_USER:-memento}" >/dev/null 2>&1; then
|
|
echo "Postgres healthy after $((i * 2))s"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 30 ]; then
|
|
echo "ERROR: Postgres not healthy after 60s"
|
|
docker compose logs postgres --tail=30
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
echo "=== Create vector extension ==="
|
|
docker compose exec -T postgres psql -U "${POSTGRES_USER:-memento}" -d "${POSTGRES_DB:-memento}" -c "CREATE EXTENSION IF NOT EXISTS vector;"
|
|
|
|
echo "=== Dump database before migration ==="
|
|
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}" --clean --if-exists | gzip > "$DUMP_FILE"
|
|
DUMP_SIZE=$(stat -c%s "$DUMP_FILE")
|
|
if [ "$DUMP_SIZE" -lt 1048576 ]; then
|
|
echo "ERROR: Dump is only $DUMP_SIZE bytes (< 1MB). Aborting deploy!"
|
|
exit 1
|
|
fi
|
|
echo "Backup saved: $DUMP_FILE ($(( DUMP_SIZE / 1024 ))KB)"
|
|
|
|
echo "=== Building app images ==="
|
|
docker compose build memento-note
|
|
docker compose build mcp-server
|
|
|
|
echo "=== Starting app containers with new images ==="
|
|
docker compose up -d --remove-orphans --force-recreate memento-note mcp-server
|
|
docker compose ps
|
|
echo "=== Deployed commit ==="
|
|
git log -1 --oneline
|
|
docker inspect memento-web --format 'Image: {{.Image}} Created: {{.Created}}'
|
|
ENDSSH
|
|
|
|
- name: Wait for app to be healthy
|
|
id: health-check
|
|
run: |
|
|
echo "Waiting up to 180s for http://192.168.1.190 ..."
|
|
for i in $(seq 1 36); do
|
|
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 -L http://192.168.1.190/ || echo "000")
|
|
if [ "$CODE" != "000" ] && [ "$CODE" -lt 500 ]; then
|
|
echo "App OK (HTTP $CODE) after $((i * 5))s"
|
|
echo "healthy=true" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
echo " [$((i * 5))s] HTTP $CODE"
|
|
sleep 5
|
|
done
|
|
echo "healthy=false" >> $GITHUB_OUTPUT
|
|
echo "Timeout! Derniers logs :"
|
|
ssh root@192.168.1.190 "docker logs memento-web --tail=50"
|
|
exit 0
|
|
|
|
- name: Rollback on failure
|
|
if: steps.health-check.outputs.healthy == 'false'
|
|
run: |
|
|
ssh root@192.168.1.190 << 'ENDSSH'
|
|
echo "=== ROLLBACK: Restoring previous image ==="
|
|
docker tag memento-note_memento-note:rollback memento-note_memento-note:latest
|
|
cd /opt/memento && docker compose up -d --force-recreate memento-note
|
|
echo "Rollback complete"
|
|
ENDSSH
|
|
|
|
- name: Notify Telegram
|
|
if: always()
|
|
run: |
|
|
HEALTHY="${{ steps.health-check.outputs.healthy }}"
|
|
if [ "$HEALTHY" = "true" ]; then
|
|
MSG="✅ Memento deploy SUCCESS%nBranch: main%nCommit: ${{ github.sha }}"
|
|
else
|
|
MSG="❌ Memento deploy FAILED%nBranch: main%nCommit: ${{ github.sha }}%nAction: rollback to previous image"
|
|
fi
|
|
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
|
|
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
|
|
-d text="$(printf "$MSG")" \
|
|
-d parse_mode="HTML" || true
|
|
|
|
- name: Fail if unhealthy
|
|
if: steps.health-check.outputs.healthy == 'false'
|
|
run: exit 1
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
run: |
|
|
ssh root@192.168.1.190 << 'ENDSSH'
|
|
docker image prune -f
|
|
cd /opt/memento/backups 2>/dev/null && ls -t pre-migrate-*.sql.gz | tail -n +11 | xargs -r rm -f && echo "Old backups cleaned" || true
|
|
ENDSSH
|