Some checks failed
Deploy to Production / Deploy to 192.168.1.190 (push) Has been cancelled
Docker: - Restrict PostgreSQL port to 127.0.0.1 only (not exposed to LAN) - Add APP_BASE_URL for MCP server to reach Next.js via Docker network - Fix MCP healthcheck (remove always-passing fallback) - Add resource limits to mcp-server container Dockerfile: - Remove full node_modules copy (standalone already includes deps) Reduces image size by ~500MB+ Config: - Add MCP_SERVER_MODE and MCP_SERVER_URL to deploy.sh and .env.docker.example - Deploy script now auto-sets MCP_SERVER_URL based on NEXTAUTH_URL Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
298 lines
8.3 KiB
Bash
Executable File
298 lines
8.3 KiB
Bash
Executable File
#!/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 "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
|