fix(deploy): exécuter sur le serveur + build CI réutilisé (~2 min)
Le deploy SSH depuis ubuntu-24.04 ne rebuildait pas sur 190 (image d'hier). Déploiement sur runner docker-host, artifact Next.js de la CI, Dockerfile.prebuilt, script deploy-prod.sh. workflow_run supprimé au profit de needs:[ci] même fichier. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -68,3 +68,122 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Pack web artifact for deploy
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
working-directory: memento-note
|
||||
run: |
|
||||
tar czf ../web-artifact.tgz \
|
||||
.next/standalone .next/static public prisma \
|
||||
node_modules/.prisma node_modules/@prisma node_modules/prisma \
|
||||
docker-entrypoint.sh
|
||||
|
||||
- name: Upload web artifact
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
working-directory: ${{ github.workspace }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web-artifact
|
||||
path: web-artifact.tgz
|
||||
retention-days: 2
|
||||
|
||||
deploy:
|
||||
name: Deploy production (on server)
|
||||
needs: [ci]
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
runs-on: docker-host
|
||||
steps:
|
||||
- name: Sync deploy scripts on server
|
||||
run: |
|
||||
cd /opt/memento
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
|
||||
- name: Download web artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: web-artifact
|
||||
|
||||
- name: Update .env.docker
|
||||
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: |
|
||||
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"
|
||||
|
||||
- name: Deploy on 192.168.1.190
|
||||
env:
|
||||
ARTIFACT_TGZ: ${{ github.workspace }}/web-artifact.tgz
|
||||
EXPECTED_COMMIT: ${{ github.sha }}
|
||||
run: bash /opt/memento/scripts/deploy-prod.sh
|
||||
|
||||
@@ -1,37 +1,23 @@
|
||||
name: Deploy to Production
|
||||
name: Deploy to Production (manual)
|
||||
|
||||
# Déploiement auto = job "deploy" dans ci.yaml (après CI, sur runner docker-host).
|
||||
# Ce workflow sert uniquement au déclenchement manuel sans repasser par la CI.
|
||||
|
||||
# Deploy only after CI succeeds (needs: [ci] does NOT work across workflow files).
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["CI"]
|
||||
types: [completed]
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Build and Deploy
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
||||
name: Deploy production (manual)
|
||||
runs-on: docker-host
|
||||
steps:
|
||||
- name: Resolve target commit
|
||||
id: target
|
||||
- name: Sync repo on server
|
||||
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
|
||||
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
|
||||
cd /opt/memento
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
|
||||
- name: Update .env.docker from Gitea vars & secrets
|
||||
- name: Update .env.docker
|
||||
env:
|
||||
APP_URL: ${{ vars.APP_URL }}
|
||||
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
||||
@@ -67,176 +53,49 @@ jobs:
|
||||
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"
|
||||
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"
|
||||
|
||||
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 ==="
|
||||
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 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) ==="
|
||||
git log -1 --oneline
|
||||
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
|
||||
|
||||
- name: Verify deployed commit on app (port 3000)
|
||||
id: health-check
|
||||
- name: Deploy (full build, no CI artifact)
|
||||
env:
|
||||
EXPECTED_SHA: ${{ steps.target.outputs.sha }}
|
||||
run: |
|
||||
echo "Expected commit: $EXPECTED_SHA"
|
||||
for i in $(seq 1 36); do
|
||||
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)
|
||||
ACTUAL=$(echo "$BODY" | jq -r '.commit // empty' 2>/dev/null || true)
|
||||
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
|
||||
exit 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
echo "healthy=false" >> $GITHUB_OUTPUT
|
||||
echo "ERROR: App on :3000 does not serve expected commit."
|
||||
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
|
||||
|
||||
- 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%nCommit: ${{ steps.target.outputs.sha }}"
|
||||
else
|
||||
MSG="❌ Memento deploy FAILED%nCommit: ${{ steps.target.outputs.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
|
||||
EXPECTED_COMMIT: ${{ github.sha }}
|
||||
run: bash /opt/memento/scripts/deploy-prod.sh
|
||||
|
||||
@@ -28,7 +28,7 @@ services:
|
||||
memento-note:
|
||||
build:
|
||||
context: ./memento-note
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: ${MEMENTO_DOCKERFILE:-Dockerfile}
|
||||
args:
|
||||
GIT_COMMIT: ${GIT_COMMIT:-unknown}
|
||||
container_name: memento-web
|
||||
|
||||
36
memento-note/Dockerfile.prebuilt
Normal file
36
memento-note/Dockerfile.prebuilt
Normal file
@@ -0,0 +1,36 @@
|
||||
# Image rapide : le build Next.js est déjà fait en CI (artifact).
|
||||
FROM node:22-bookworm-slim AS runner
|
||||
ARG GIT_COMMIT=unknown
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV GIT_COMMIT=$GIT_COMMIT
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
openssl \
|
||||
postgresql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd --system --gid 1001 nodejs \
|
||||
&& useradd --system --uid 1001 --gid nodejs nextjs
|
||||
|
||||
COPY public ./public
|
||||
RUN mkdir -p ./data/uploads/notes ./data/backups && chown -R nextjs:nodejs ./data
|
||||
|
||||
COPY .next/standalone ./
|
||||
COPY .next/static ./.next/static
|
||||
|
||||
COPY prisma ./prisma
|
||||
COPY node_modules/.prisma ./node_modules/.prisma
|
||||
COPY node_modules/@prisma ./node_modules/@prisma
|
||||
COPY node_modules/prisma ./node_modules/prisma
|
||||
|
||||
COPY docker-entrypoint.sh ./docker-entrypoint.sh
|
||||
RUN chmod +x ./docker-entrypoint.sh
|
||||
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
80
scripts/deploy-prod.sh
Executable file
80
scripts/deploy-prod.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
# Déploiement prod sur 192.168.1.190 — exécuter SUR LE SERVEUR (runner docker-host).
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="${DEPLOY_ROOT:-/opt/memento}"
|
||||
ARTIFACT_TGZ="${ARTIFACT_TGZ:-}"
|
||||
EXPECTED_COMMIT="${EXPECTED_COMMIT:-}"
|
||||
|
||||
cd "$ROOT"
|
||||
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
|
||||
|
||||
docker compose up -d postgres
|
||||
for i in $(seq 1 30); do
|
||||
docker compose exec -T postgres pg_isready -U "${POSTGRES_USER:-memento}" >/dev/null 2>&1 && break
|
||||
[ "$i" -eq 30 ] && { echo "Postgres not ready"; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
|
||||
docker compose exec -T postgres psql -U "${POSTGRES_USER:-memento}" -d "${POSTGRES_DB:-memento}" -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}" --clean --if-exists | 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
|
||||
|
||||
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
|
||||
else
|
||||
echo "=== Full docker build (no artifact) ==="
|
||||
export MEMENTO_DOCKERFILE=Dockerfile
|
||||
fi
|
||||
|
||||
docker compose build memento-note
|
||||
|
||||
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 mcp-server 2>/dev/null || true
|
||||
|
||||
nginx -t 2>/dev/null && systemctl reload nginx 2>/dev/null || true
|
||||
|
||||
for i in $(seq 1 24); do
|
||||
BODY=$(curl -sf --max-time 3 http://127.0.0.1:3000/api/build-info 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"
|
||||
docker compose ps
|
||||
exit 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "ERROR: build-info=$ACTUAL expected=$GIT_COMMIT"
|
||||
docker logs memento-web --tail=40
|
||||
exit 1
|
||||
Reference in New Issue
Block a user