fix(deploy): déclencher après CI et vérifier le commit déployé
All checks were successful
CI / Lint, Test & Build (push) Successful in 12m13s
All checks were successful
CI / Lint, Test & Build (push) Successful in 12m13s
Le job deploy référençait needs:[ci] dans un autre workflow (inefficace sur Gitea). Déclenchement via workflow_run après CI réussie, empreinte GIT_COMMIT dans l'image, endpoint /api/build-info et health check sur 127.0.0.1:3000 avec comparaison du SHA attendu. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,17 +1,29 @@
|
|||||||
name: Deploy to Production
|
name: Deploy to Production
|
||||||
|
|
||||||
|
# Deploy only after CI succeeds (needs: [ci] does NOT work across workflow files).
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_run:
|
||||||
branches:
|
workflows: ["CI"]
|
||||||
- main
|
types: [completed]
|
||||||
|
branches: [main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Build and Deploy
|
name: Build and Deploy
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
needs: [ci]
|
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Resolve target commit
|
||||||
|
id: target
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
SHA="${{ github.sha }}"
|
||||||
|
else
|
||||||
|
SHA="${{ github.event.workflow_run.head_sha }}"
|
||||||
|
fi
|
||||||
|
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Deploying commit: $SHA"
|
||||||
- name: Setup SSH
|
- name: Setup SSH
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
@@ -152,34 +164,45 @@ jobs:
|
|||||||
echo "Backup saved: $DUMP_FILE ($(( DUMP_SIZE / 1024 ))KB)"
|
echo "Backup saved: $DUMP_FILE ($(( DUMP_SIZE / 1024 ))KB)"
|
||||||
|
|
||||||
echo "=== Building app images ==="
|
echo "=== Building app images ==="
|
||||||
|
export GIT_COMMIT="$(git rev-parse HEAD)"
|
||||||
|
echo "GIT_COMMIT=$GIT_COMMIT"
|
||||||
|
export COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1
|
||||||
docker compose build memento-note
|
docker compose build memento-note
|
||||||
docker compose build mcp-server
|
docker compose build mcp-server
|
||||||
|
|
||||||
echo "=== Starting app containers with new images ==="
|
echo "=== Starting app containers with new images ==="
|
||||||
docker compose up -d --remove-orphans --force-recreate memento-note mcp-server
|
docker compose up -d --remove-orphans --force-recreate memento-note mcp-server
|
||||||
docker compose ps
|
docker compose ps
|
||||||
echo "=== Deployed commit ==="
|
echo "=== Deployed commit (git) ==="
|
||||||
git log -1 --oneline
|
git log -1 --oneline
|
||||||
docker inspect memento-web --format 'Image: {{.Image}} Created: {{.Created}}'
|
docker inspect memento-web --format 'Image: {{.Image}} Created: {{.Created}}'
|
||||||
|
|
||||||
|
echo "=== Reload reverse proxy (if nginx on host) ==="
|
||||||
|
nginx -t 2>/dev/null && systemctl reload nginx 2>/dev/null || true
|
||||||
ENDSSH
|
ENDSSH
|
||||||
|
|
||||||
- name: Wait for app to be healthy
|
- name: Verify deployed commit on app (port 3000)
|
||||||
id: health-check
|
id: health-check
|
||||||
|
env:
|
||||||
|
EXPECTED_SHA: ${{ steps.target.outputs.sha }}
|
||||||
run: |
|
run: |
|
||||||
echo "Waiting up to 180s for http://192.168.1.190 ..."
|
echo "Expected commit: $EXPECTED_SHA"
|
||||||
for i in $(seq 1 36); do
|
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")
|
BODY=$(ssh root@192.168.1.190 "curl -sf --max-time 5 http://127.0.0.1:3000/api/build-info" 2>/dev/null || true)
|
||||||
if [ "$CODE" != "000" ] && [ "$CODE" -lt 500 ]; then
|
ACTUAL=$(echo "$BODY" | jq -r '.commit // empty' 2>/dev/null || true)
|
||||||
echo "App OK (HTTP $CODE) after $((i * 5))s"
|
echo " [$((i * 5))s] build-info commit=$ACTUAL"
|
||||||
|
if [ -n "$ACTUAL" ] && [ "$ACTUAL" = "$EXPECTED_SHA" ]; then
|
||||||
|
echo "Deploy verified: container serves commit $ACTUAL"
|
||||||
echo "healthy=true" >> $GITHUB_OUTPUT
|
echo "healthy=true" >> $GITHUB_OUTPUT
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
echo " [$((i * 5))s] HTTP $CODE"
|
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
echo "healthy=false" >> $GITHUB_OUTPUT
|
echo "healthy=false" >> $GITHUB_OUTPUT
|
||||||
echo "Timeout! Derniers logs :"
|
echo "ERROR: App on :3000 does not serve expected commit."
|
||||||
ssh root@192.168.1.190 "docker logs memento-web --tail=50"
|
echo " expected=$EXPECTED_SHA"
|
||||||
|
echo " got=$ACTUAL"
|
||||||
|
ssh root@192.168.1.190 "cd /opt/memento && git log -1 --oneline && docker logs memento-web --tail=40"
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
- name: Rollback on failure
|
- name: Rollback on failure
|
||||||
@@ -197,9 +220,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
HEALTHY="${{ steps.health-check.outputs.healthy }}"
|
HEALTHY="${{ steps.health-check.outputs.healthy }}"
|
||||||
if [ "$HEALTHY" = "true" ]; then
|
if [ "$HEALTHY" = "true" ]; then
|
||||||
MSG="✅ Memento deploy SUCCESS%nBranch: main%nCommit: ${{ github.sha }}"
|
MSG="✅ Memento deploy SUCCESS%nCommit: ${{ steps.target.outputs.sha }}"
|
||||||
else
|
else
|
||||||
MSG="❌ Memento deploy FAILED%nBranch: main%nCommit: ${{ github.sha }}%nAction: rollback to previous image"
|
MSG="❌ Memento deploy FAILED%nCommit: ${{ steps.target.outputs.sha }}%nAction: rollback to previous image"
|
||||||
fi
|
fi
|
||||||
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
|
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
|
||||||
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
|
-d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./memento-note
|
context: ./memento-note
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
GIT_COMMIT: ${GIT_COMMIT:-unknown}
|
||||||
container_name: memento-web
|
container_name: memento-web
|
||||||
env_file:
|
env_file:
|
||||||
- .env.docker
|
- .env.docker
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ RUN npx prisma generate
|
|||||||
# Stage 2: Build
|
# Stage 2: Build
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
FROM node:22-bookworm-slim AS builder
|
FROM node:22-bookworm-slim AS builder
|
||||||
|
ARG GIT_COMMIT=unknown
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
@@ -38,10 +39,12 @@ RUN npm run build
|
|||||||
# Stage 3: Runner
|
# Stage 3: Runner
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
FROM node:22-bookworm-slim AS runner
|
FROM node:22-bookworm-slim AS runner
|
||||||
|
ARG GIT_COMMIT=unknown
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV GIT_COMMIT=$GIT_COMMIT
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
openssl \
|
openssl \
|
||||||
|
|||||||
10
memento-note/app/api/build-info/route.ts
Normal file
10
memento-note/app/api/build-info/route.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
|
/** Public build fingerprint for deploy verification (no secrets). */
|
||||||
|
export async function GET() {
|
||||||
|
return NextResponse.json({
|
||||||
|
commit: process.env.GIT_COMMIT ?? 'unknown',
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user