All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m25s
- Add DeepSeek, OpenRouter, Mistral, Z.AI, LM Studio as AI providers with editable model names via Combobox in admin settings - Fix OpenRouter broken by normalizeProvider bug in config.ts - Convert agent-created notes from Markdown to HTML (TipTap rich text) - Add Notification model + in-app notifications for agent results - Agent notification click opens the created note directly - Add note count display on notebook and inbox headers - Fix checklist toggle in card view (persist state via localCheckItems) - Add checklist creation option in tabs/list view (dropdown on + button) - Fix image description ENOENT error with HTTP fallback - Improve UI contrast across all themes (input, border, checkbox visibility) - Add font family setting (Inter vs System Default) in Appearance settings - Fix CSS font-sans variable conflict (removed dead Geist references) - Update README with new features and 8 providers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
583 lines
16 KiB
Bash
Executable File
583 lines
16 KiB
Bash
Executable File
#!/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
|