# ============================================================ # Memento - Local Deploy Script (Windows PowerShell) # ============================================================ # Usage: # .\scripts\deploy-local.ps1 # Full setup # .\scripts\deploy-local.ps1 -EnvOnly # Generate .env only # .\scripts\deploy-local.ps1 -Start # Start the app (dev or prod) # .\scripts\deploy-local.ps1 -Migrate # Run database migrations # .\scripts\deploy-local.ps1 -Install # Install npm dependencies # ============================================================ [CmdletBinding()] param( [switch]$EnvOnly = $false, [switch]$Start = $false, [switch]$Migrate = $false, [switch]$Install = $false, [switch]$Full = $false ) $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $ProjectDir = Split-Path -Parent $ScriptDir $AppDir = Join-Path $ProjectDir "memento-note" $McpDir = Join-Path $ProjectDir "mcp-server" $EnvFile = Join-Path $AppDir ".env" $McpEnvFile = Join-Path $McpDir ".env" # ----------------------------------------------------------- # Helpers # ----------------------------------------------------------- function Write-Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Blue } function Write-Ok($msg) { Write-Host "[OK] $msg" -ForegroundColor Green } function Write-Warn($msg) { Write-Host "[WARN] $msg" -ForegroundColor Yellow } function Write-Err($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 } function Write-Step($msg) { Write-Host "`n==> $msg" -ForegroundColor Cyan } function Get-RandomSecret { $bytes = New-Object byte[] 32 [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($bytes) [Convert]::ToBase64String($bytes) } function Ask-Input { param([string]$Prompt, [string]$Default = "") if ($Default) { Write-Host " ? $Prompt [$Default]: " -ForegroundColor Cyan -NoNewline } else { Write-Host " ? $Prompt: " -ForegroundColor Cyan -NoNewline } $result = Read-Host if ([string]::IsNullOrWhiteSpace($result)) { $result = $Default } return $result } function Ask-Required { param([string]$Prompt) while ($true) { Write-Host " ? $Prompt (required): " -ForegroundColor Cyan -NoNewline $result = Read-Host if (-not [string]::IsNullOrWhiteSpace($result)) { return $result } Write-Host " This field is required." -ForegroundColor Red } } function Ask-Email { param([string]$Prompt) while ($true) { Write-Host " ? $Prompt (required): " -ForegroundColor Cyan -NoNewline $result = Read-Host if (-not [string]::IsNullOrWhiteSpace($result) -and $result -match '^[^@]+@[^@]+\.[^@]+$') { return $result } Write-Host " Please enter a valid email address." -ForegroundColor Red } } # ----------------------------------------------------------- # Check dependencies # ----------------------------------------------------------- function Check-Deps { Write-Step "Checking dependencies..." $missing = @() # Node.js $node = Get-Command node -ErrorAction SilentlyContinue if ($node) { $nodeVersion = & node -v 2>$null Write-Ok "Node.js $nodeVersion" } else { $missing += "node" } # npm $npm = Get-Command npm -ErrorAction SilentlyContinue if ($npm) { $npmVersion = & npm -v 2>$null Write-Ok "npm $npmVersion" } else { $missing += "npm" } # PostgreSQL $psql = Get-Command psql -ErrorAction SilentlyContinue if ($psql) { $pgVersion = & psql --version 2>$null | Select-Object -First 1 Write-Ok "PostgreSQL client $pgVersion" } else { $missing += "psql" } if ($missing.Count -gt 0) { Write-Host "" Write-Warn "Missing dependencies: $($missing -join ', ')" Write-Host "" Write-Host " Installation instructions:" -ForegroundColor White Write-Host "" foreach ($dep in $missing) { switch ($dep) { "node" { Write-Host " Node.js + npm:" Write-Host " Download: https://nodejs.org/" Write-Host " Winget: winget install OpenJS.NodeJS.LTS" Write-Host "" } "npm" { Write-Host " npm:" Write-Host " Usually included with Node.js installer" Write-Host "" } "psql" { Write-Host " PostgreSQL:" Write-Host " Download: https://www.postgresql.org/download/windows/" Write-Host " Winget: winget install PostgreSQL.PostgreSQL" Write-Host "" } } } $cont = Ask-Input "Continue anyway?" "N" if ($cont -notmatch "^[Yy]") { exit 1 } } } # ----------------------------------------------------------- # Generate .env # ----------------------------------------------------------- function Generate-Env { Write-Step "Configuring Memento for local development" if (Test-Path $EnvFile) { Write-Warn ".env already exists at $EnvFile" $confirm = Ask-Input "Overwrite?" "N" if ($confirm -notmatch "^[Yy]") { Write-Info "Keeping existing .env" return } } Write-Host "" Write-Host " This wizard will guide you through the configuration." -ForegroundColor White Write-Host " Press Enter to accept defaults in [brackets]." -ForegroundColor White Write-Host "" # ---- Database ---- Write-Step "Database configuration" $dbHost = Ask-Input "PostgreSQL host" "localhost" $dbPort = Ask-Input "PostgreSQL port" "5432" $dbName = Ask-Input "PostgreSQL database name" "memento" $dbUser = Ask-Input "PostgreSQL username" "memento" $dbPass = Ask-Input "PostgreSQL password" "memento" $dbUrl = "postgresql://${dbUser}:${dbPass}@${dbHost}:${dbPort}/${dbName}" # Check DB connectivity if (Get-Command psql -ErrorAction SilentlyContinue) { Write-Step "Testing database connection..." $env:PGPASSWORD = $dbPass try { & psql -h $dbHost -p $dbPort -U $dbUser -d $dbName -c "SELECT 1" 2>&1 | Out-Null Write-Ok "Database connection successful" } catch { Write-Warn "Could not connect to database. It may not exist yet." Write-Host "" Write-Host " Create it with:" Write-Host " createdb $dbName" Write-Host " psql -c `"CREATE USER $dbUser WITH PASSWORD '$dbPass';`"" Write-Host " psql -c `"GRANT ALL PRIVILEGES ON DATABASE $dbName TO $dbUser;`"" Write-Host "" $cont = Ask-Input "Continue anyway?" "Y" if ($cont -match "^[Nn]") { exit 1 } } Remove-Item Env:PGPASSWORD } # ---- Core ---- Write-Step "Core configuration" $url = Ask-Input "App URL (NEXTAUTH_URL)" "http://localhost:3000" $secret = Get-RandomSecret Write-Info "Auto-generated NEXTAUTH_SECRET" $adminEmail = Ask-Email "Admin email (first user with this email becomes ADMIN)" $allowReg = Ask-Input "Allow public registration" "true" if ($allowReg -match "^[Yy]|^[Yy]es|^true|^1") { $allowReg = "true" } else { $allowReg = "false" } # ---- AI Provider ---- Write-Step "AI Provider configuration" Write-Host " Choose your AI provider:" Write-Host " 1) OpenAI" Write-Host " 2) Ollama (local)" Write-Host " 3) Custom OpenAI-compatible (OpenRouter, Groq, Together, etc.)" Write-Host " 4) Skip AI configuration" Write-Host "" $aiChoice = Ask-Input "Choice" "4" $aiTagsProvider = $aiTagsModel = $aiEmbedProvider = $aiEmbedModel = "" $aiChatProvider = $aiChatModel = $openaiKey = $customKey = $customUrl = $ollamaUrl = "" switch ($aiChoice) { "1" { $aiTagsProvider = "openai"; $aiTagsModel = "gpt-4o-mini" $aiEmbedProvider = "openai"; $aiEmbedModel = "text-embedding-3-small" $aiChatProvider = "openai"; $aiChatModel = "gpt-4o-mini" $openaiKey = Ask-Required "OpenAI API Key" } "2" { $aiTagsProvider = "ollama"; $aiTagsModel = "granite4:latest" $aiEmbedProvider = "ollama"; $aiEmbedModel = "embeddinggemma:latest" $aiChatProvider = "ollama"; $aiChatModel = "granite4:latest" $ollamaUrl = Ask-Input "Ollama base URL" "http://localhost:11434" } "3" { $aiTagsProvider = "custom" $aiEmbedProvider = "custom" $aiChatProvider = "custom" $customKey = Ask-Required "Custom provider API Key" $customUrl = Ask-Required "Custom provider base URL" $aiTagsModel = Ask-Input "Model for tags" "gpt-4o-mini" $aiEmbedModel = Ask-Input "Model for embeddings" "text-embedding-3-small" $aiChatModel = Ask-Input "Model for chat" "gpt-4o-mini" } "4" { Write-Info "Skipping AI configuration. You can configure it later in the admin panel." } default { Write-Err "Invalid choice" } } # ---- MCP ---- Write-Step "MCP Server configuration (optional)" $mcpEnable = Ask-Input "Configure MCP server?" "no" $mcpEnable = ($mcpEnable -match "^[Yy]|^[Yy]es|^true|^1") $mcpMode = "sse"; $mcpPort = "3001"; $mcpServerUrl = "" if ($mcpEnable) { $mcpMode = Ask-Input "MCP mode (sse or stdio)" "sse" $mcpPort = Ask-Input "MCP port" "3001" $mcpServerUrl = "http://localhost:${mcpPort}" } # ---- Email ---- Write-Step "Email configuration (optional, needed for password reset)" Write-Host " Choose an email provider:" Write-Host " 1) Resend" Write-Host " 2) SMTP" Write-Host " 3) Skip" Write-Host "" $emailChoice = Ask-Input "Choice" "3" $resendKey = $smtpHost = $smtpPort = $smtpUser = $smtpPass = $smtpFrom = "" switch ($emailChoice) { "1" { $resendKey = Ask-Required "Resend API Key" } "2" { $smtpHost = Ask-Required "SMTP Host" $smtpPort = Ask-Input "SMTP Port" "587" $smtpUser = Ask-Required "SMTP Username" $smtpPass = Ask-Required "SMTP Password" $smtpFrom = Ask-Required "SMTP From email" } } # ---- Write .env ---- Write-Step "Writing .env" $envContent = @" # ============================================================================= # Memento - Local Environment (auto-generated by deploy-local.ps1) # ============================================================================= # Generated on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') # ============================================================================= # Core DATABASE_URL="$dbUrl" NEXTAUTH_SECRET="$secret" NEXTAUTH_URL="$url" ADMIN_EMAIL="$adminEmail" ALLOW_REGISTRATION="$allowReg" "@ if ($aiChoice -ne "4") { $envContent += @" # AI - Tags AI_PROVIDER_TAGS=$aiTagsProvider AI_MODEL_TAGS="$aiTagsModel" # AI - Embeddings AI_PROVIDER_EMBEDDING=$aiEmbedProvider AI_MODEL_EMBEDDING="$aiEmbedModel" # AI - Chat AI_PROVIDER_CHAT=$aiChatProvider AI_MODEL_CHAT="$aiChatModel" "@ if ($openaiKey) { $envContent += "`nOPENAI_API_KEY=`"$openaiKey`"" } if ($customKey) { $envContent += "`nCUSTOM_OPENAI_API_KEY=`"$customKey`"`nCUSTOM_OPENAI_BASE_URL=`"$customUrl`"" } if ($ollamaUrl) { $envContent += "`nOLLAMA_BASE_URL=`"$ollamaUrl`"" } } if ($mcpEnable) { $envContent += @" # MCP Server MCP_SERVER_MODE="$mcpMode" MCP_SERVER_URL="$mcpServerUrl" "@ } else { $envContent += @" # MCP Server (disabled) MCP_SERVER_MODE="disabled" "@ } if ($resendKey) { $envContent += "`n`n# Email - Resend`nRESEND_API_KEY=`"$resendKey`"" } if ($smtpHost) { $envContent += @" # Email - SMTP SMTP_HOST="$smtpHost" SMTP_PORT="$smtpPort" SMTP_USER="$smtpUser" SMTP_PASS="$smtpPass" SMTP_FROM="$smtpFrom" "@ } $envContent | Set-Content -Path $EnvFile -Encoding UTF8 Write-Ok ".env created at $EnvFile" # MCP server .env if ($mcpEnable) { $mcpContent = @" # ============================================================================= # MCP Server - Local Environment (auto-generated by deploy-local.ps1) # ============================================================================= DATABASE_URL="$dbUrl" MCP_MODE="$mcpMode" PORT="$mcpPort" APP_BASE_URL="$url" "@ $mcpContent | Set-Content -Path $McpEnvFile -Encoding UTF8 Write-Ok ".env created at $McpEnvFile" } Write-Host "" Write-Host " Configuration summary:" -ForegroundColor White Write-Host " URL: $url" Write-Host " Admin email: $adminEmail" Write-Host " Database: $dbHost`:$dbPort/$dbName" Write-Host " AI provider: $(if ($aiChoice -eq '4') { 'skipped' } else { $aiTagsProvider })" Write-Host " MCP server: $(if ($mcpEnable) { "enabled ($mcpMode)" } else { 'disabled' })" Write-Host " Email: $(if ($emailChoice -eq '3') { 'skipped' } else { 'configured' })" } # ----------------------------------------------------------- # Install dependencies # ----------------------------------------------------------- function Install-Deps { Write-Step "Installing dependencies..." Push-Location $AppDir if (-not (Test-Path "node_modules")) { Write-Info "Running npm install..." & npm install Write-Ok "Dependencies installed" } else { Write-Info "node_modules exists, checking for updates..." & npm install } Pop-Location } # ----------------------------------------------------------- # Run migrations # ----------------------------------------------------------- function Run-Migrations { if (-not (Test-Path $EnvFile)) { Write-Err ".env not found. Run: .\scripts\deploy-local.ps1 -EnvOnly" } Write-Step "Running database migrations..." Push-Location $AppDir try { & npx prisma migrate deploy 2>&1 } catch { Write-Warn "migrate deploy failed, trying db push..." try { & npx prisma db push --skip-generate 2>&1 } catch { Write-Err "Database migration failed" } } Write-Ok "Database migrations complete" Pop-Location } # ----------------------------------------------------------- # Start app # ----------------------------------------------------------- function Start-App { if (-not (Test-Path $EnvFile)) { Write-Err ".env not found. Run: .\scripts\deploy-local.ps1 -EnvOnly" } Push-Location $AppDir Write-Host "" Write-Host " Choose mode:" Write-Host " 1) Development (npm run dev, with hot reload)" Write-Host " 2) Production (npm run build + npm start)" Write-Host "" $mode = Ask-Input "Choice" "1" switch ($mode) { "2" { Write-Step "Building for production..." & npm run build Write-Step "Starting in production mode..." Write-Info "Starting server on http://localhost:3000" Write-Info "Press Ctrl+C to stop" Write-Host "" & npm start } default { Write-Step "Starting in development mode..." Write-Info "Starting dev server on http://localhost:3000" Write-Info "Press Ctrl+C to stop" Write-Host "" & npm run dev } } Pop-Location } # ----------------------------------------------------------- # Full setup # ----------------------------------------------------------- function Full-Setup { Check-Deps Generate-Env Install-Deps Run-Migrations Write-Host "" Write-Host "============================================" -ForegroundColor Green Write-Host " Setup complete!" -ForegroundColor Green Write-Host "============================================" -ForegroundColor Green Write-Host "" $admEmail = (Select-String -Path $EnvFile -Pattern '^ADMIN_EMAIL=(.+)$').Matches[0].Groups[1].Value -replace '"','' $appUrl = (Select-String -Path $EnvFile -Pattern '^NEXTAUTH_URL=(.+)$').Matches[0].Groups[1].Value -replace '"','' Write-Host " Next steps:" -ForegroundColor White Write-Host " 1. Start the app: .\scripts\deploy-local.ps1 -Start" Write-Host " 2. Open: $appUrl" Write-Host " 3. Register with: $admEmail" -ForegroundColor White Write-Host " 4. That account will automatically get ADMIN role" Write-Host "" } # ----------------------------------------------------------- # Main # ----------------------------------------------------------- if ($EnvOnly) { Check-Deps Generate-Env } elseif ($Start) { Start-App } elseif ($Migrate) { Run-Migrations } elseif ($Install) { Install-Deps } else { # Default: full setup Full-Setup }