#!/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" # 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 "Initializing database..." docker compose exec memento-note npx prisma db push --skip-generate 2>/dev/null || \ warn "DB push failed (may already be synced)" 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") if [ "$user_count" = "0" ]; then echo "" warn "No users found. Register at $(grep NEXTAUTH_URL "$ENV_FILE" | cut -d= -f2 | tr -d '"')/register" warn "Then run: docker compose exec memento-note npx tsx scripts/grant-all-admins.ts" 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