From b8c85be40f60a58477cab28ac09d565113eb98eb Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 28 Jun 2026 13:15:55 +0000 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20.env.docker=20resilient=20?= =?UTF-8?q?=E2=80=94=20no=20rm=20-f,=20sanity-check=20vars=20critiques?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Supprime rm -f (causait la perte de ~23 vars a chaque deploy) - upsert ecrit KEY=value sans quotes (compatible Docker Compose v2) - CRLF strip avant ecriture (sed s/\r$//) - Sanity-check post-upsert: abort si NEXTAUTH_SECRET/AUTH_GOOGLE_ID/etc manquantes - Header ## AUTO-MANAGED BY CI ## en tete de fichier genere - deploy-prod.sh: sanity-check pre-deploy (NEXTAUTH_URL/SECRET/GOOGLE_ID/SECRET) - Ajoute .env.docker.example (reference complete de toutes les vars) - Ajoute MCP_SERVER_MODE/MCP_SERVER_URL manquantes dans deploy.yaml --- .env.docker.example | 171 +++++++++++++---------------------- .gitea/workflows/ci.yaml | 15 ++- .gitea/workflows/deploy.yaml | 15 ++- scripts/deploy-prod.sh | 11 ++- 4 files changed, 102 insertions(+), 110 deletions(-) diff --git a/.env.docker.example b/.env.docker.example index 35170b3..6d76ad1 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -1,121 +1,80 @@ -# ============================================================================= -# Memento - Docker Environment Configuration -# ============================================================================= -# Copy this file to .env.docker and update with your values. -# This file is read by docker-compose.yml via env_file directive. -# cp .env.docker.example .env.docker +## AUTO-MANAGED BY CI — do not edit manually ## +## This is a reference template. Real values come from Gitea vars/secrets. ## -# ============================================================================= -# APPLICATION URL (REQUIRED) -# ============================================================================= -# Change to your server IP or domain -# Examples: -# IP: http://192.168.1.190:3000 -# Domain: http://notes.yourdomain.com -# HTTPS: https://notes.yourdomain.com -NEXTAUTH_URL="http://localhost:3000" +# Core +NEXTAUTH_URL=https://memento-note.com +NEXTAUTH_SECRET= +ADMIN_EMAIL=admin@example.com +ALLOW_REGISTRATION=true -# ============================================================================= -# AUTHENTICATION SECRET (REQUIRED) -# ============================================================================= -# Generate with: openssl rand -base64 32 -NEXTAUTH_SECRET="changethisinproduction" - -# ============================================================================= -# REGISTRATION & ADMIN -# ============================================================================= -# Set to "false" to disable public registration (default: true) -# ALLOW_REGISTRATION=true - -# Admin email - The first user registering with this email gets ADMIN role (REQUIRED) -# ADMIN_EMAIL="admin@yourdomain.com" - -# Google OAuth — both required to show "Continue with Google" on /login -# Redirect URI in Google Console: {NEXTAUTH_URL}/api/auth/callback/google -# AUTH_GOOGLE_ID="....apps.googleusercontent.com" -# AUTH_GOOGLE_SECRET="GOCSPX-..." - -# ============================================================================= -# POSTGRESQL CONFIGURATION -# ============================================================================= -POSTGRES_PORT=5432 -POSTGRES_DB=memento +# PostgreSQL (used by docker-compose to construct DATABASE_URL) POSTGRES_USER=memento -POSTGRES_PASSWORD=memento +POSTGRES_PASSWORD= +POSTGRES_DB=memento +POSTGRES_PORT=5433 -# ============================================================================= -# MCP SERVER CONFIGURATION -# ============================================================================= -# Mode: 'stdio' (Claude Desktop, Cline) or 'sse' (N8N, HTTP) -MCP_MODE="stdio" -MCP_PORT="3001" -# Frontend MCP settings (for the MCP settings panel in the web UI) -# MCP_SERVER_MODE="sse" -# MCP_SERVER_URL="http://YOUR_IP:3001" - -# ============================================================================= -# AI PROVIDER - TAGS GENERATION -# ============================================================================= -# Options: ollama, openai, custom +# AI - Tags AI_PROVIDER_TAGS=ollama -AI_MODEL_TAGS="granite4:latest" +AI_MODEL_TAGS=granite4:latest -# ============================================================================= -# AI PROVIDER - EMBEDDINGS -# ============================================================================= -# Options: ollama, openai, custom +# AI - Embeddings AI_PROVIDER_EMBEDDING=ollama -AI_MODEL_EMBEDDING="embeddinggemma:latest" +AI_MODEL_EMBEDDING=embeddinggemma:latest -# ============================================================================= -# AI PROVIDER - CHAT (optional, falls back to AI_PROVIDER_TAGS) -# ============================================================================= -# AI_PROVIDER_CHAT=ollama -# AI_MODEL_CHAT="granite4:latest" +# AI - Chat +AI_PROVIDER_CHAT=ollama +AI_MODEL_CHAT=granite4:latest -# ============================================================================= -# OLLAMA CONFIGURATION (if provider = ollama) -# ============================================================================= -# Docker service: http://ollama:11434 -# Host machine: http://host.docker.internal:11434 -# Remote server: http://YOUR_SERVER_IP:11434 -OLLAMA_BASE_URL="http://ollama:11434" +# AI - Custom OpenAI (OpenRouter etc.) +CUSTOM_OPENAI_BASE_URL=https://openrouter.ai/api/v1 +CUSTOM_OPENAI_API_KEY= +OPENAI_API_KEY= -# ============================================================================= -# OPENAI CONFIGURATION (if provider = openai) -# ============================================================================= -# OPENAI_API_KEY="sk-..." +# AI - Ollama +OLLAMA_BASE_URL=http://ollama:11434 -# ============================================================================= -# CUSTOM OPENAI-COMPATIBLE PROVIDER (if provider = custom) -# ============================================================================= -# Compatible with: OpenRouter, Groq, Together AI, Mistral, etc. -# OpenRouter: https://openrouter.ai/api/v1 -# Groq: https://api.groq.com/openai/v1 -# Together: https://api.together.xyz/v1 -# Mistral: https://api.mistral.ai/v1 -# CUSTOM_OPENAI_API_KEY="your-api-key" -# CUSTOM_OPENAI_BASE_URL="https://openrouter.ai/api/v1" +# Redis (set by CI, do not override) +REDIS_HOST=redis -# ============================================================================= -# EMAIL / SMTP (optional, required for password reset) -# ============================================================================= -# SMTP_HOST="smtp.gmail.com" -# SMTP_PORT="587" -# SMTP_USER="your-email@gmail.com" -# SMTP_PASS="your-app-password" -# SMTP_FROM="noreply@memento.app" +# Email +EMAIL_PROVIDER=resend +SMTP_FROM=noreply@memento-note.com +RESEND_API_KEY= +SMTP_HOST= +SMTP_PORT= +SMTP_USER= +SMTP_PASS= +SMTP_SECURE= +SMTP_IGNORE_CERT= -# ============================================================================= -# RESEND EMAIL (alternative to SMTP, optional) -# ============================================================================= -# RESEND_API_KEY="re_..." +# Google OAuth +AUTH_GOOGLE_ID= +AUTH_GOOGLE_SECRET= -# ───────────────────────────────────────────────────────────────────────────── -# Brainstorm / Socket.io -# ───────────────────────────────────────────────────────────────────────────── -SOCKET_PORT=3005 +# MCP Server +MCP_MODE=sse +MCP_PORT=3001 +MCP_SERVER_MODE=sse +MCP_SERVER_URL=https://memento-note.com/mcp +MCP_API_KEY= + +# Web Search +WEB_SEARCH_PROVIDER=searxng +SEARXNG_URL=http://192.168.1.190:8888 +BRAVE_SEARCH_API_KEY= +JINA_API_KEY= + +# Socket (realtime) +SOCKET_INTERNAL_KEY= +SOCKET_PORT=3002 SOCKET_HTTP_PORT=3003 -SOCKET_INTERNAL_KEY=change-this-to-a-random-secret -SOCKET_INTERNAL_URL=http://memento-socket:3003 -NEXT_PUBLIC_SOCKET_URL=https://memento-note.com +SOCKET_INTERNAL_URL=http://memento-socket:3002 +NEXT_PUBLIC_SOCKET_URL=wss://memento-note.com/ws + +# Telegram notifications +TELEGRAM_BOT_TOKEN= +TELEGRAM_CHAT_ID= + +# Monitoring +METRICS_TOKEN= +GRAFANA_ADMIN_PASSWORD= diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 54eccbe..b2581c2 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -177,8 +177,9 @@ jobs: MCP_API_KEY: ${{ secrets.MCP_API_KEY }} run: | ENV_FILE="/opt/memento/.env.docker" - rm -f "$ENV_FILE" touch "$ENV_FILE" + sed -i 's/\r$//' "$ENV_FILE" + echo "## AUTO-MANAGED BY CI — do not edit manually ##" > "$ENV_FILE" upsert() { local key="$1" val="$2" [ -z "$val" ] && return @@ -215,6 +216,8 @@ jobs: upsert SMTP_IGNORE_CERT "$SMTP_IGNORE_CERT" upsert MCP_MODE "$MCP_MODE" upsert MCP_PORT "$MCP_PORT" + upsert MCP_SERVER_MODE "$MCP_MODE" + upsert MCP_SERVER_URL "${APP_URL}/mcp" upsert WEB_SEARCH_PROVIDER "$WEB_SEARCH_PROVIDER" upsert SEARXNG_URL "$SEARXNG_URL" upsert BRAVE_SEARCH_API_KEY "$BRAVE_SEARCH_API_KEY" @@ -233,6 +236,16 @@ jobs: upsert MCP_API_KEY "$MCP_API_KEY" # Write metrics token file for Prometheus (same secret) [ -n "$METRICS_TOKEN" ] && echo "$METRICS_TOKEN" > /opt/memento/monitoring/metrics-token && chmod 600 /opt/memento/monitoring/metrics-token || true + # Sanity-check: abort if a critical var is missing + for required in NEXTAUTH_URL NEXTAUTH_SECRET AUTH_GOOGLE_ID AUTH_GOOGLE_SECRET \ + AI_PROVIDER_TAGS AI_MODEL_TAGS AI_PROVIDER_EMBEDDING AI_MODEL_EMBEDDING \ + AI_PROVIDER_CHAT AI_MODEL_CHAT MCP_SERVER_URL; do + grep -q "^${required}=" "$ENV_FILE" || { + echo "ERROR: required var $required missing in $ENV_FILE — check Gitea vars/secrets" + exit 1 + } + done + echo "env.docker sanity-check passed ($(wc -l < "$ENV_FILE") lines)" - name: Deploy on 192.168.1.190 env: diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index e7321d7..d6ce15a 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -66,8 +66,9 @@ jobs: MCP_API_KEY: ${{ secrets.MCP_API_KEY }} run: | ENV_FILE="/opt/memento/.env.docker" - rm -f "$ENV_FILE" touch "$ENV_FILE" + sed -i 's/\r$//' "$ENV_FILE" + echo "## AUTO-MANAGED BY CI — do not edit manually ##" > "$ENV_FILE" upsert() { local key="$1" val="$2" [ -z "$val" ] && return @@ -103,6 +104,8 @@ jobs: upsert SMTP_IGNORE_CERT "$SMTP_IGNORE_CERT" upsert MCP_MODE "$MCP_MODE" upsert MCP_PORT "$MCP_PORT" + upsert MCP_SERVER_MODE "$MCP_MODE" + upsert MCP_SERVER_URL "${APP_URL}/mcp" upsert WEB_SEARCH_PROVIDER "$WEB_SEARCH_PROVIDER" upsert SEARXNG_URL "$SEARXNG_URL" upsert BRAVE_SEARCH_API_KEY "$BRAVE_SEARCH_API_KEY" @@ -121,6 +124,16 @@ jobs: upsert GRAFANA_ADMIN_PASSWORD "$GRAFANA_ADMIN_PASSWORD" upsert MCP_API_KEY "$MCP_API_KEY" [ -n "$METRICS_TOKEN" ] && echo "$METRICS_TOKEN" > /opt/memento/monitoring/metrics-token && chmod 600 /opt/memento/monitoring/metrics-token || true + # Sanity-check: abort if a critical var is missing + for required in NEXTAUTH_URL NEXTAUTH_SECRET AUTH_GOOGLE_ID AUTH_GOOGLE_SECRET \ + AI_PROVIDER_TAGS AI_MODEL_TAGS AI_PROVIDER_EMBEDDING AI_MODEL_EMBEDDING \ + AI_PROVIDER_CHAT AI_MODEL_CHAT MCP_SERVER_URL; do + grep -q "^${required}=" "$ENV_FILE" || { + echo "ERROR: required var $required missing in $ENV_FILE — check Gitea vars/secrets" + exit 1 + } + done + echo "env.docker sanity-check passed ($(wc -l < "$ENV_FILE") lines)" - name: Deploy (full build, no CI artifact) env: diff --git a/scripts/deploy-prod.sh b/scripts/deploy-prod.sh index f15b20c..3880f3b 100755 --- a/scripts/deploy-prod.sh +++ b/scripts/deploy-prod.sh @@ -127,9 +127,16 @@ HEALTH_CHECK_SLEEP_SECONDS=5 cd "$ROOT" -# Sanitize .env.docker: Docker Compose v2 rejects ANY quote character +# Pre-deploy sanity-check: .env.docker must have critical vars if [ -f "$ROOT/.env.docker" ]; then - tr -d '"' < "$ROOT/.env.docker" > "$ROOT/.env.docker.tmp" && mv "$ROOT/.env.docker.tmp" "$ROOT/.env.docker" + sed -i 's/\r$//' "$ROOT/.env.docker" + for required in NEXTAUTH_URL NEXTAUTH_SECRET AUTH_GOOGLE_ID AUTH_GOOGLE_SECRET; do + grep -q "^${required}=" "$ROOT/.env.docker" || { + echo "ERROR: $required missing in .env.docker — aborting deploy" + telegram_notify "failure" "Missing $required in .env.docker" + exit 1 + } + done fi load_env_docker