Deployment Guide
Complete guide to deploy Monaime on a new server from scratch. Supabase + VPS + Caddy + PM2.
1 Requirements
Server (VPS)
- Ubuntu 22+ or Debian 12+
Node.js 20+with npmPM2process managerCaddyweb server (auto TLS)- Min 1 GB RAM, 10 GB disk
External Services
Supabaseproject (PostgreSQL + Auth)OpenAI APIkey (GPT-4o access)Domain namewith DNS A recordFixer.ioAPI key (optional, for currency rates)Apple Developeraccount (for iOS push)
Local Development
Node.js 20+Xcode 15+(for iOS)rsync(for deploy)sshpass(optional, for scripted deploy)
Supabase Setup
- Enable Email Auth provider
- Enable
pgvectorextension - Get: Project URL, Anon Key, Service Key
- Run schema SQL in SQL Editor
2 Step 1: Supabase Project Setup
Create Supabase Project
Go to supabase.com, create a new project. Note the following credentials from Settings → API:
# From Supabase Dashboard → Settings → API
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_KEY=eyJ...
# From Supabase Dashboard → Settings → Auth → JWT Secret
JWT_SECRET=your-jwt-secret-min-32-charsEnable Extensions
In Supabase Dashboard → SQL Editor, run:
-- Required for conversation memory embeddings (RAG)
CREATE EXTENSION IF NOT EXISTS vector;Apply Database Schema
Copy the full contents of Server/sql/schema.sql into Supabase SQL Editor and run. This creates all tables, indexes, functions, triggers, and RLS policies.
The complete SQL is in Section 7 of this page.
Create User Sync Trigger
When a user signs up via Supabase Auth, automatically create a record in the public.users table:
-- Auto-create public.users row when auth.users signs up
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO public.users (id, email, name)
VALUES (
NEW.id,
NEW.email,
COALESCE(NEW.raw_user_meta_data->>'name', split_part(NEW.email, '@', 1))
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION handle_new_user();3 Step 2: Server (VPS) Setup
Install Dependencies
# Update system
apt update && apt upgrade -y
# Install Node.js 20
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# Install PM2 globally
npm install -g pm2
# Install Caddy
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy.list
apt update && apt install -y caddyCreate Project Directories
mkdir -p /var/www/finance-chat-backend/dist
mkdir -p /var/www/financechat-web/distCreate Environment File
nano /var/www/finance-chat-backend/.envAdd the following:
PORT=3004
HOST=0.0.0.0
NODE_ENV=production
SUPABASE_URL=https://your-project-ref.supabase.co
SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_KEY=eyJ...
OPENAI_API_KEY=sk-...
JWT_SECRET=your-jwt-secret-min-32-chars
CORS_ORIGIN=https://app.yourdomain.com,https://yourdomain.com
# Optional
FIXER_API_KEY=your-fixer-key
AI_MODEL_FAMILY=gpt-5Configure Caddy (Reverse Proxy + TLS)
Edit /etc/caddy/Caddyfile:
api.yourdomain.com {
reverse_proxy localhost:3004
}
app.yourdomain.com {
root * /var/www/financechat-web/dist
file_server
try_files {path} /index.html
}# Restart Caddy to apply
systemctl restart caddyCaddy automatically provisions TLS certificates via Let's Encrypt.
4 Step 3: Deploy Backend
Build & Deploy (from local machine)
# Build TypeScript
cd Server
npm install
npm test -- --run
npm run build
# Copy compiled code + dependencies to server
rsync -avz --delete dist/ user@server:/var/www/finance-chat-backend/dist/
rsync -avz package.json package-lock.json user@server:/var/www/finance-chat-backend/
# Install production dependencies on server
ssh user@server "cd /var/www/finance-chat-backend && npm install --production"Start with PM2
# First time: start process
cd /var/www/finance-chat-backend
pm2 start dist/index.js --name financechat-api
pm2 save
pm2 startup
# Subsequent deploys: restart
pm2 restart financechat-api
# Check logs
pm2 logs financechat-api --lines 50 --nostream5 Step 4: Deploy Web Frontend
Build & Deploy
# Build React app
cd Web
npm install
npm test -- --run
npm run build
# Copy dist to server
rsync -avz --delete dist/ user@server:/var/www/financechat-web/dist/No restart needed — Caddy serves static files directly.
6 Step 5: iOS App Configuration
Update Config.swift
In iOS/MoneyChat/Config/Config.swift, update the API URLs and Supabase credentials:
// API endpoints
static let apiBaseURL = "https://api.yourdomain.com"
static let wsURL = "wss://api.yourdomain.com/ws/chat"
// Supabase credentials
static let supabaseURL = "https://your-project-ref.supabase.co"
static let supabaseAnonKey = "eyJ..."Update Certificate Pinning
In iOS/MoneyChat/Services/HTTPClient.swift, update the certificate pin hash for your new domain's TLS certificate.
7 Full Database Schema (SQL)
Server/sql/schema.sql. Always use that file as the source of truth. Copy its contents into Supabase SQL Editor to apply.
-- Current tables (Server/sql/schema.sql):
--
-- CORE TABLES:
-- users - User profiles (synced from Supabase Auth)
-- wallets - Bank accounts, cash, cards
-- categories - Income/expense categories (includes budget fields)
-- transactions - All financial operations
-- contacts - Debt tracking (people who owe/are owed)
-- merchants - Auto-categorization by merchant name
-- recurring_templates - Subscriptions and recurring payments
--
-- AI & NOTIFICATIONS:
-- ai_trigger_configs - Per-user notification settings
-- ai_notifications - Generated notifications (limit warnings, etc.)
-- reminders - User-created reminders
-- notification_milestones - Prevent duplicate notifications
-- conversation_memories - RAG embeddings (pgvector)
--
-- CHAT:
-- chat_messages - Conversation history
-- chat_feedback - User feedback on AI responses
-- docs_feedback - Documentation page feedback
--
-- AUTOMATION:
-- automation_rules - User-defined "if X then Y" rules
-- rule_applications - Tracks which rules applied to which transactions
-- suggestions - AI-generated recommendations
-- execution_plans - Multi-step operation plans
--
-- SYSTEM:
-- action_history - Undo functionality (max 50 per user)
-- pending_actions - Dangerous command confirmations (with TTL)
-- entity_aliases - User-defined names for wallets/categories/contacts
-- user_devices - Multi-device sync
-- device_tokens - Push notification tokens
-- currency_rates - Exchange rate cache
-- financial_health_history - Periodic financial health scores
--
-- GLOBAL (no user_id):
-- mcc_categories - MCC code to category mapping
-- merchant_patterns - Auto-detection patterns for merchants
--
-- KEY FUNCTIONS:
-- update_wallet_balance() - Atomic balance update
-- transfer_between_wallets() - Atomic transfer with row locking
-- process_recurring_templates() - Cron: create transactions from templates
-- update_merchant_usage() - Update merchant stats
-- cleanup_old_action_history() - Trigger: keep max 50 per user
-- delete_user_data() - GDPR: delete all user data
--
-- NOTE: Budgets are stored as fields on categories table:
-- budget_amount, budget_period, budget_currency
-- current_spent is computed at query time (no trigger/cron needed)
--
-- All tables have RLS enabled: auth.uid() = user_id
-- Server uses service_role key to bypass RLS8 Deploy Checklist
New Server Setup
Regular Deploy (code update)
npm test -- --runnpm run buildpm2 logs --lines 20 --nostream