#!/bin/bash set -euo pipefail # ============================================================ # Memento - Local Deploy Script (macOS / Linux) # ============================================================ # Usage: # ./scripts/deploy-local.sh # Full setup # ./scripts/deploy-local.sh --env-only # Generate .env only # ./scripts/deploy-local.sh --start # Start the app (dev or prod) # ./scripts/deploy-local.sh --stop # Stop the app # ./scripts/deploy-local.sh --migrate # Run database migrations # ============================================================ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" APP_DIR="$PROJECT_DIR/memento-note" ENV_FILE="$APP_DIR/.env" MCP_DIR="$PROJECT_DIR/mcp-server" MCP_ENV_FILE="$MCP_DIR/.env" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' 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; } step() { echo -e "\n${CYAN}${BOLD}==>${NC} ${BOLD}$*${NC}"; } # ----------------------------------------------------------- # Generate random secret # ----------------------------------------------------------- gen_secret() { if command -v openssl &>/dev/null; then openssl rand -base64 32 2>/dev/null else head -c 32 /dev/urandom | base64 fi } # ----------------------------------------------------------- # Ask helpers # ----------------------------------------------------------- ask() { local prompt="$1" local default="${2:-}" local var="$3" local result if [ -n "$default" ]; then echo -ne " ${CYAN}?${NC} ${prompt} [${default}]: " else echo -ne " ${CYAN}?${NC} ${prompt}: " fi read -r result result="${result:-$default}" eval "$var=\"\$result\"" } ask_required() { local prompt="$1" local var="$2" local result while true; do echo -ne " ${CYAN}?${NC} ${prompt} (required): " read -r result if [ -n "$result" ]; then eval "$var=\"\$result\"" return fi echo -e " ${RED}This field is required.${NC}" done } ask_email() { local prompt="$1" local var="$2" local result while true; do echo -ne " ${CYAN}?${NC} ${prompt} (required): " read -r result if [ -n "$result" ] && echo "$result" | grep -qE '^[^@]+@[^@]+\.[^@]+$'; then eval "$var=\"\$result\"" return fi echo -e " ${RED}Please enter a valid email address.${NC}" done } # ----------------------------------------------------------- # Check dependencies # ----------------------------------------------------------- check_deps() { step "Checking dependencies..." local missing=() # Node.js if command -v node &>/dev/null; then local node_version node_version=$(node -v 2>/dev/null) ok "Node.js $node_version" else missing+=("node") fi # npm if command -v npm &>/dev/null; then ok "npm $(npm -v 2>/dev/null)" else missing+=("npm") fi # PostgreSQL if command -v psql &>/dev/null; then ok "PostgreSQL client $(psql --version 2>/dev/null | head -1)" else missing+=("psql") fi # Check if PostgreSQL server is running if command -v pg_isready &>/dev/null; then if pg_isready &>/dev/null 2>&1; then ok "PostgreSQL server is running" else warn "PostgreSQL server does not seem to be running." echo " Try: brew services start postgresql (macOS) or sudo systemctl start postgresql (Linux)" fi fi if [ ${#missing[@]} -gt 0 ]; then echo "" warn "Missing dependencies: ${missing[*]}" echo "" echo -e " ${BOLD}Installation instructions:${NC}" echo "" for dep in "${missing[@]}"; do case "$dep" in node|npm) echo " Node.js + npm:" echo " macOS: brew install node" echo " Linux: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs" echo " Or: https://nodejs.org/" echo "" ;; psql) echo " PostgreSQL:" echo " macOS: brew install postgresql@16 && brew services start postgresql@16" echo " Linux: sudo apt install -y postgresql postgresql-contrib" echo " Or: https://www.postgresql.org/download/" echo "" ;; esac done echo -ne " ${CYAN}?${NC} Continue anyway? [y/N]: " read -r cont [[ "$cont" != "y" && "$cont" != "Y" ]] && exit 1 fi } # ----------------------------------------------------------- # Generate .env interactively # ----------------------------------------------------------- generate_env() { step "Configuring Memento for local development" if [ -f "$ENV_FILE" ]; then warn ".env already exists at $ENV_FILE" echo -ne " ${CYAN}?${NC} Overwrite? [y/N]: " read -r confirm [[ "$confirm" != "y" && "$confirm" != "Y" ]] && { info "Keeping existing .env"; return 0; } fi echo "" echo -e "${BOLD} This wizard will guide you through the configuration.${NC}" echo -e "${BOLD} Press Enter to accept defaults in [brackets].${NC}" echo "" # ---- Database ---- step "Database configuration" local db_host="localhost" local db_port="5432" local db_name="memento" local db_user="memento" local db_pass="memento" ask "PostgreSQL host" "$db_host" db_host ask "PostgreSQL port" "$db_port" db_port ask "PostgreSQL database name" "$db_name" db_name ask "PostgreSQL username" "$db_user" db_user ask "PostgreSQL password" "$db_pass" db_pass local db_url="postgresql://${db_user}:${db_pass}@${db_host}:${db_port}/${db_name}" # Check DB connectivity if command -v pg_isready &>/dev/null; then step "Testing database connection..." if PGPASSWORD="$db_pass" psql -h "$db_host" -p "$db_port" -U "$db_user" -d "$db_name" -c "SELECT 1" &>/dev/null 2>&1; then ok "Database connection successful" else warn "Could not connect to database. It may not exist yet." echo "" echo " Create it with:" echo " createdb $db_name" echo " psql -c \"CREATE USER $db_user WITH PASSWORD '$db_pass';\"" echo " psql -c \"GRANT ALL PRIVILEGES ON DATABASE $db_name TO $db_user;\"" echo "" echo -ne " ${CYAN}?${NC} Continue anyway? [Y/n]: " read -r cont [[ "$cont" == "n" || "$cont" == "N" ]] && exit 1 fi fi # ---- Core ---- step "Core configuration" local url="http://localhost:3000" ask "App URL (NEXTAUTH_URL)" "$url" url local secret secret=$(gen_secret) info "Auto-generated NEXTAUTH_SECRET" local admin_email ask_email "Admin email (first user with this email becomes ADMIN)" admin_email local allow_reg="true" ask "Allow public registration" "$allow_reg" allow_reg case "$allow_reg" in [Yy]|[Yy]es|true|1) allow_reg="true" ;; *) allow_reg="false" ;; esac # ---- AI Provider ---- step "AI Provider configuration" echo " Choose your AI provider:" echo " 1) OpenAI" echo " 2) Ollama (local)" echo " 3) Custom OpenAI-compatible (OpenRouter, Groq, Together, etc.)" echo " 4) Skip AI configuration" echo "" local ai_choice="4" ask "Choice" "$ai_choice" ai_choice local ai_tags_provider="" ai_tags_model="" local ai_embed_provider="" ai_embed_model="" local ai_chat_provider="" ai_chat_model="" local openai_key="" custom_key="" custom_url="" ollama_url="" case "$ai_choice" in 1) 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" ask_required "OpenAI API Key" openai_key ;; 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" ollama_url="http://localhost:11434" ask "Ollama base URL" "$ollama_url" ollama_url ;; 3) ai_tags_provider="custom" ai_embed_provider="custom" ai_chat_provider="custom" ask_required "Custom provider API Key" custom_key ask_required "Custom provider base URL" custom_url ask "Model for tags" "gpt-4o-mini" ai_tags_model ask "Model for embeddings" "text-embedding-3-small" ai_embed_model ask "Model for chat" "gpt-4o-mini" ai_chat_model ;; 4) info "Skipping AI configuration. You can configure it later in the admin panel." ;; *) error "Invalid choice" ;; esac # ---- MCP ---- step "MCP Server configuration (optional)" local mcp_enable="no" ask "Configure MCP server?" "$mcp_enable" mcp_enable case "$mcp_enable" in [Yy]|[Yy]es|true|1) mcp_enable="yes" ;; *) mcp_enable="no" ;; esac local mcp_mode="sse" mcp_port="3001" mcp_server_url="" if [ "$mcp_enable" = "yes" ]; then ask "MCP mode (sse or stdio)" "$mcp_mode" mcp_mode ask "MCP port" "$mcp_port" mcp_port mcp_server_url="http://localhost:${mcp_port}" fi # ---- Email ---- step "Email configuration (optional, needed for password reset)" echo " Choose an email provider:" echo " 1) Resend" echo " 2) SMTP" echo " 3) Skip" echo "" local email_choice="3" ask "Choice" "$email_choice" email_choice local resend_key="" smtp_host="" smtp_port="" smtp_user="" smtp_pass="" smtp_from="" case "$email_choice" in 1) ask_required "Resend API Key" resend_key ;; 2) ask_required "SMTP Host" smtp_host ask "SMTP Port" "587" smtp_port ask_required "SMTP Username" smtp_user ask_required "SMTP Password" smtp_pass ask_required "SMTP From email" smtp_from ;; esac # ---- Write .env ---- step "Writing .env" cat > "$ENV_FILE" << EOF # ============================================================================= # Memento - Local Environment (auto-generated by deploy-local.sh) # ============================================================================= # Generated on $(date '+%Y-%m-%d %H:%M:%S') # ============================================================================= # Core DATABASE_URL="${db_url}" NEXTAUTH_SECRET="${secret}" NEXTAUTH_URL="${url}" ADMIN_EMAIL="${admin_email}" ALLOW_REGISTRATION="${allow_reg}" EOF # AI config if [ "$ai_choice" != "4" ]; then cat >> "$ENV_FILE" << EOF # 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}" EOF if [ -n "$openai_key" ]; then echo "OPENAI_API_KEY=\"${openai_key}\"" >> "$ENV_FILE" fi if [ -n "$custom_key" ]; then echo "CUSTOM_OPENAI_API_KEY=\"${custom_key}\"" >> "$ENV_FILE" echo "CUSTOM_OPENAI_BASE_URL=\"${custom_url}\"" >> "$ENV_FILE" fi if [ -n "$ollama_url" ]; then echo "OLLAMA_BASE_URL=\"${ollama_url}\"" >> "$ENV_FILE" fi fi # MCP config if [ "$mcp_enable" = "yes" ]; then cat >> "$ENV_FILE" << EOF # MCP Server MCP_SERVER_MODE="${mcp_mode}" MCP_SERVER_URL="${mcp_server_url}" EOF else cat >> "$ENV_FILE" << EOF # MCP Server (disabled) MCP_SERVER_MODE="disabled" EOF fi # Email config if [ -n "$resend_key" ]; then cat >> "$ENV_FILE" << EOF # Email - Resend RESEND_API_KEY="${resend_key}" EOF fi if [ -n "$smtp_host" ]; then cat >> "$ENV_FILE" << EOF # Email - SMTP SMTP_HOST="${smtp_host}" SMTP_PORT="${smtp_port}" SMTP_USER="${smtp_user}" SMTP_PASS="${smtp_pass}" SMTP_FROM="${smtp_from}" EOF fi ok ".env created at $ENV_FILE" # Also generate MCP server .env if MCP enabled if [ "$mcp_enable" = "yes" ]; then cat > "$MCP_ENV_FILE" << EOF # ============================================================================= # MCP Server - Local Environment (auto-generated by deploy-local.sh) # ============================================================================= DATABASE_URL="${db_url}" MCP_MODE="${mcp_mode}" PORT="${mcp_port}" APP_BASE_URL="${url}" EOF ok ".env created at $MCP_ENV_FILE" fi echo "" echo -e " ${BOLD}Configuration summary:${NC}" echo " URL: $url" echo " Admin email: $admin_email" echo " Database: $db_host:$db_port/$db_name" echo " AI provider: $([ "$ai_choice" = "4" ] && echo "skipped" || echo "$ai_tags_provider")" echo " MCP server: $([ "$mcp_enable" = "yes" ] && echo "enabled ($mcp_mode)" || echo "disabled")" echo " Email: $([ "$email_choice" = "3" ] && echo "skipped" || echo "configured")" } # ----------------------------------------------------------- # Install dependencies # ----------------------------------------------------------- install_deps() { step "Installing dependencies..." cd "$APP_DIR" if [ ! -d "node_modules" ]; then info "Running npm install..." npm install ok "Dependencies installed" else info "node_modules exists, checking for updates..." npm install fi } # ----------------------------------------------------------- # Run migrations # ----------------------------------------------------------- run_migrations() { [ -f "$ENV_FILE" ] || error ".env not found. Run: $0 --env-only" step "Running database migrations..." cd "$APP_DIR" npx prisma migrate deploy 2>&1 || { warn "migrate deploy failed, trying db push..." npx prisma db push --skip-generate 2>&1 || error "Database migration failed" } ok "Database migrations complete" } # ----------------------------------------------------------- # Start the app # ----------------------------------------------------------- start_app() { [ -f "$ENV_FILE" ] || error ".env not found. Run: $0 --env-only" cd "$APP_DIR" echo "" echo " Choose mode:" echo " 1) Development (npm run dev, with hot reload)" echo " 2) Production (npm run build + npm start)" echo "" local mode="1" ask "Choice" "$mode" mode case "$mode" in 2) step "Building for production..." npm run build step "Starting in production mode..." info "Starting server on http://localhost:3000" info "Press Ctrl+C to stop" echo "" npm start ;; *) step "Starting in development mode..." info "Starting dev server on http://localhost:3000" info "Press Ctrl+C to stop" echo "" npm run dev ;; esac } # ----------------------------------------------------------- # Full setup # ----------------------------------------------------------- full_setup() { check_deps generate_env install_deps run_migrations echo "" echo -e "${GREEN}${BOLD}============================================${NC}" echo -e "${GREEN}${BOLD} Setup complete!${NC}" echo -e "${GREEN}${BOLD}============================================${NC}" echo "" local admin_email admin_email=$(grep '^ADMIN_EMAIL=' "$ENV_FILE" | cut -d= -f2 | tr -d '"') echo -e " ${BOLD}Next steps:${NC}" echo " 1. Start the app: $0 --start" echo " 2. Open: $(grep '^NEXTAUTH_URL=' "$ENV_FILE" | cut -d= -f2 | tr -d '"')" echo -e " 3. Register with: ${BOLD}${admin_email}${NC}" echo " 4. That account will automatically get ADMIN role" echo "" } # ----------------------------------------------------------- # Main # ----------------------------------------------------------- ACTION="${1:---full}" case "$ACTION" in --env-only) check_deps generate_env ;; --start) start_app ;; --migrate) run_migrations ;; --install) install_deps ;; --stop) warn "For local deployment, stop the server with Ctrl+C" ;; --full) full_setup ;; *) echo "Usage: $0 [--env-only | --start | --migrate | --install | --full | --stop]" echo "" echo " --env-only Generate .env interactively" echo " --start Start the app (dev or prod mode)" echo " --migrate Run database migrations" echo " --install Install npm dependencies" echo " --full Full setup: env + install + migrate (default)" echo " --stop Reminder to use Ctrl+C" exit 1 ;; esac