#!/bin/bash set -euo pipefail # ============================================================ # Memento - Deploy Script # ============================================================ # Usage: ./scripts/deploy.sh [--env-only | --build | --full] # # --env-only : Generate .env.docker interactively # --build : Build and start containers (default) # --full : env + build + first-time setup # ============================================================ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" ENV_FILE="$PROJECT_DIR/.env.docker" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' info() { echo -e "${BLUE}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } # ----------------------------------------------------------- # Generate .env.docker # ----------------------------------------------------------- generate_env() { if [ -f "$ENV_FILE" ]; then warn "$ENV_FILE already exists." read -rp "Overwrite? [y/N] " confirm [[ "$confirm" != "y" && "$confirm" != "Y" ]] && return 0 fi info "Generating $ENV_FILE ..." # Auto-generate NEXTAUTH_SECRET local secret secret=$(openssl rand -base64 32 2>/dev/null || head -c 32 /dev/urandom | base64) # Auto-generate secure postgres password local pg_pass pg_pass=$(openssl rand -hex 16 2>/dev/null || head -c 16 /dev/urandom | hexdump -v -e '/1 "%02x"') # Ask for URL local url="http://192.168.1.190" read -rp "NEXTAUTH_URL [$url]: " input_url [ -n "$input_url" ] && url="$input_url" # Ask for AI provider echo "" echo "AI Provider:" echo " 1) OpenRouter (recommended, works out of the box)" echo " 2) Ollama (requires ollama container)" echo " 3) OpenAI" echo " 4) DeepSeek" echo " 5) Skip AI configuration" read -rp "Choice [1]: " ai_choice ai_choice="${ai_choice:-1}" local ai_tags_provider="openrouter" local ai_tags_model="google/gemma-3-27b-it" local ai_embed_provider="openrouter" local ai_embed_model="text-embedding-3-small" local ai_chat_provider="openrouter" local ai_chat_model="google/gemma-3-27b-it" local custom_api_key="" local custom_base_url="" case "$ai_choice" in 1) read -rp "OpenRouter API Key: " custom_api_key [ -z "$custom_api_key" ] && error "API key is required" custom_base_url="https://openrouter.ai/api/v1" ;; 2) ai_tags_provider="ollama" ai_tags_model="granite4:latest" ai_embed_provider="ollama" ai_embed_model="embeddinggemma:latest" ai_chat_provider="ollama" ai_chat_model="granite4:latest" custom_base_url="http://ollama:11434" ;; 3) ai_tags_provider="openai" ai_tags_model="gpt-4o-mini" ai_embed_provider="openai" ai_embed_model="text-embedding-3-small" ai_chat_provider="openai" ai_chat_model="gpt-4o-mini" read -rp "OpenAI API Key: " custom_api_key [ -z "$custom_api_key" ] && error "API key is required" ;; 4) ai_tags_provider="deepseek" ai_tags_model="deepseek-chat" ai_embed_provider="openrouter" ai_embed_model="text-embedding-3-small" ai_chat_provider="deepseek" ai_chat_model="deepseek-chat" read -rp "DeepSeek API Key: " custom_api_key [ -z "$custom_api_key" ] && error "API key is required" custom_base_url="https://api.deepseek.com/v1" ;; 5) info "Skipping AI configuration" ;; *) error "Invalid choice" ;; esac # Ask for email provider echo "" echo "Email provider:" echo " 1) Resend" echo " 2) SMTP" echo " 3) Skip" read -rp "Choice [3]: " email_choice email_choice="${email_choice:-3}" local resend_key="" local smtp_config="" case "$email_choice" in 1) read -rp "Resend API Key: " resend_key ;; 2) local smtp_host smtp_port smtp_user smtp_pass smtp_from read -rp "SMTP Host: " smtp_host read -rp "SMTP Port [587]: " smtp_port; smtp_port="${smtp_port:-587}" read -rp "SMTP User: " smtp_user read -rp "SMTP Password: " smtp_pass read -rp "SMTP From: " smtp_from smtp_config="SMTP_HOST=\"$smtp_host\" SMTP_PORT=\"$smtp_port\" SMTP_USER=\"$smtp_user\" SMTP_PASS=\"$smtp_pass\" SMTP_FROM=\"$smtp_from\"" ;; esac # Write .env.docker cat > "$ENV_FILE" << ENVEOF # ============================================================================= # Memento - Docker Environment (auto-generated) # ============================================================================= NEXTAUTH_URL="${url}" NEXTAUTH_SECRET="${secret}" ALLOW_REGISTRATION="true" # PostgreSQL POSTGRES_PORT=5432 POSTGRES_DB=memento POSTGRES_USER=memento POSTGRES_PASSWORD="${pg_pass}" # MCP Server MCP_MODE="sse" MCP_PORT="3001" MCP_SERVER_MODE="sse" MCP_SERVER_URL="${url}:3001" # AI - Tags AI_PROVIDER_TAGS=${ai_tags_provider} AI_MODEL_TAGS="${ai_tags_model}" # AI - Embeddings AI_PROVIDER_EMBEDDING=${ai_embed_provider} AI_MODEL_EMBEDDING="${ai_embed_model}" # AI - Chat AI_PROVIDER_CHAT=${ai_chat_provider} AI_MODEL_CHAT="${ai_chat_model}" ENVEOF # Add API key config if applicable if [ -n "$custom_api_key" ]; then case "$ai_choice" in 1) echo "CUSTOM_OPENAI_API_KEY=\"${custom_api_key}\"" >> "$ENV_FILE" echo "CUSTOM_OPENAI_BASE_URL=\"${custom_base_url}\"" >> "$ENV_FILE" ;; 3) echo "OPENAI_API_KEY=\"${custom_api_key}\"" >> "$ENV_FILE" ;; 4) echo "CUSTOM_OPENAI_API_KEY=\"${custom_api_key}\"" >> "$ENV_FILE" echo "CUSTOM_OPENAI_BASE_URL=\"${custom_base_url}\"" >> "$ENV_FILE" ;; esac fi # Add Ollama URL if using ollama if [ "$ai_choice" = "2" ]; then echo "OLLAMA_BASE_URL=\"http://ollama:11434\"" >> "$ENV_FILE" fi # Add email config if [ -n "$resend_key" ]; then echo "RESEND_API_KEY=\"${resend_key}\"" >> "$ENV_FILE" fi if [ -n "$smtp_config" ]; then echo "" >> "$ENV_FILE" echo "$smtp_config" >> "$ENV_FILE" fi ok "$ENV_FILE created" echo "" info "Contents:" grep -v "KEY\|PASSWORD\|SECRET" "$ENV_FILE" | sed 's/^/ /' echo " (sensitive values hidden)" } # ----------------------------------------------------------- # Build and deploy # ----------------------------------------------------------- deploy() { [ -f "$ENV_FILE" ] || error "$ENV_FILE not found. Run with --env-only first." cd "$PROJECT_DIR" info "Pulling latest code..." git pull origin main 2>/dev/null || warn "Could not pull (maybe already up to date or no git)" info "Building containers..." docker compose build --parallel 2>&1 info "Starting containers..." docker compose up -d 2>&1 info "Waiting for healthchecks..." local retries=0 local max_retries=30 while [ $retries -lt $max_retries ]; do local unhealthy unhealthy=$(docker compose ps --format '{{.Status}}' 2>/dev/null | grep -c -v "healthy\|Up" || true) if [ "$unhealthy" -eq 0 ]; then break fi retries=$((retries + 1)) sleep 2 printf "." done echo "" info "Database migrations are handled by the container entrypoint on every start." info "The entrypoint handles fresh installs, updates, and P3005 baseline recovery automatically." echo "" echo "==========================================" docker compose ps echo "==========================================" echo "" ok "Deploy complete!" info "App: $(grep NEXTAUTH_URL "$ENV_FILE" | cut -d= -f2 | tr -d '"')" # Show admin setup hint if first time local user_count user_count=$(docker compose exec -T postgres psql -U memento -d memento -t -c 'SELECT COUNT(*) FROM "User"' 2>/dev/null | tr -d ' ' || echo "0") local admin_email admin_email=$(grep '^ADMIN_EMAIL=' "$ENV_FILE" 2>/dev/null | cut -d= -f2 | tr -d '"' || echo "") if [ "$user_count" = "0" ]; then echo "" warn "No users found." if [ -n "$admin_email" ]; then info "Register at $(grep NEXTAUTH_URL "$ENV_FILE" | cut -d= -f2 | tr -d '"')/register" info "Use email: $admin_email (will automatically get ADMIN role)" else info "Register at $(grep NEXTAUTH_URL "$ENV_FILE" | cut -d= -f2 | tr -d '"')/register" warn "ADMIN_EMAIL is not set. Set it in .env.docker for automatic admin role assignment." fi fi } # ----------------------------------------------------------- # Main # ----------------------------------------------------------- ACTION="${1:---build}" case "$ACTION" in --env-only) generate_env ;; --build) deploy ;; --full) generate_env echo "" deploy ;; *) echo "Usage: $0 [--env-only | --build | --full]" exit 1 ;; esac