diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..f296caf --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,375 @@ +# HOA LedgerIQ — Deployment Guide + +**Version:** 2026.3.2 (beta) +**Last updated:** 2026-03-02 + +--- + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [Deploy to a Fresh Docker Server](#deploy-to-a-fresh-docker-server) +3. [Backup the Local Test Database](#backup-the-local-test-database) +4. [Restore a Backup into the Staged Environment](#restore-a-backup-into-the-staged-environment) +5. [Running Migrations on the Staged Environment](#running-migrations-on-the-staged-environment) +6. [Verifying the Deployment](#verifying-the-deployment) +7. [Environment Variable Reference](#environment-variable-reference) + +--- + +## Prerequisites + +On the **target server**, ensure the following are installed: + +| Tool | Minimum Version | +|-----------------|-----------------| +| Docker Engine | 24+ | +| Docker Compose | v2+ | +| Git | 2.x | +| `psql` (client) | 15+ *(optional, for manual DB work)* | + +The app runs five containers — nginx, backend (NestJS), frontend (Vite/React), +PostgreSQL 15, and Redis 7. Total memory footprint is roughly **1–2 GB** idle. + +--- + +## Deploy to a Fresh Docker Server + +### 1. Clone the repository + +```bash +ssh your-staging-server + +git clone /opt/hoa-ledgeriq +cd /opt/hoa-ledgeriq +``` + +### 2. Create the environment file + +Copy the example and fill in real values: + +```bash +cp .env.example .env +nano .env # or vi, your choice +``` + +**Required changes from defaults:** + +```dotenv +# --- CHANGE THESE --- +POSTGRES_PASSWORD= +JWT_SECRET= + +# Database URL must match the password above +DATABASE_URL=postgresql://hoafinance:@postgres:5432/hoafinance + +# AI features (get a key from build.nvidia.com) +AI_API_KEY=nvapi-xxxxxxxxxxxx + +# --- Usually fine as-is --- +POSTGRES_USER=hoafinance +POSTGRES_DB=hoafinance +REDIS_URL=redis://redis:6379 +NODE_ENV=development # keep as development for staging +AI_API_URL=https://integrate.api.nvidia.com/v1 +AI_MODEL=qwen/qwen3.5-397b-a17b +AI_DEBUG=false +``` + +> **Tip:** Generate secrets quickly: +> ```bash +> openssl rand -hex 32 # good for JWT_SECRET +> openssl rand -base64 24 # good for POSTGRES_PASSWORD +> ``` + +### 3. Build and start the stack + +```bash +docker compose up -d --build +``` + +This will: +- Build the backend and frontend images +- Pull `postgres:15-alpine`, `redis:7-alpine`, and `nginx:alpine` +- Initialize the PostgreSQL database with the shared schema (`db/init/00-init.sql`) +- Start all five services on the `hoanet` bridge network + +### 4. Wait for healthy services + +```bash +docker compose ps +``` + +All five containers should show `Up` (postgres and redis should also show +`(healthy)`). If the backend is restarting, check logs: + +```bash +docker compose logs backend --tail=50 +``` + +### 5. (Optional) Seed with demo data + +If deploying a fresh environment for testing and you want the Sunrise Valley +HOA demo tenant: + +```bash +docker compose exec -T postgres psql -U hoafinance -d hoafinance < db/seed/seed.sql +``` + +This creates: +- Platform admin: `admin@hoaledgeriq.com` / `password123` +- Tenant admin: `admin@sunrisevalley.org` / `password123` +- Tenant viewer: `viewer@sunrisevalley.org` / `password123` + +### 6. Access the application + +| Service | URL | +|-----------|--------------------------------| +| App (UI) | `http://` | +| API | `http:///api` | +| Postgres | `:5432` (direct) | + +> **Note:** For production, add an SSL-terminating proxy (Caddy, Traefik, or +> an nginx TLS config) in front of port 80. + +--- + +## Backup the Local Test Database + +### Full database dump (recommended) + +From your **local development machine** where the app is currently running: + +```bash +cd /path/to/HOA_Financial_Platform + +# Dump the entire database (all schemas, roles, data) +docker compose exec -T postgres pg_dump \ + -U hoafinance \ + -d hoafinance \ + --no-owner \ + --no-privileges \ + --format=custom \ + -f /tmp/hoafinance_backup.dump + +# Copy the dump file out of the container +docker compose cp postgres:/tmp/hoafinance_backup.dump ./hoafinance_backup.dump +``` + +The `--format=custom` flag produces a compressed binary format that supports +selective restore. The file is typically 50–80% smaller than plain SQL. + +### Alternative: Plain SQL dump + +If you prefer a human-readable SQL file: + +```bash +docker compose exec -T postgres pg_dump \ + -U hoafinance \ + -d hoafinance \ + --no-owner \ + --no-privileges \ + > hoafinance_backup.sql +``` + +### Backup a single tenant schema + +To export just one tenant (e.g., Pine Creek HOA): + +```bash +docker compose exec -T postgres pg_dump \ + -U hoafinance \ + -d hoafinance \ + --no-owner \ + --no-privileges \ + --schema=tenant_pine_creek_hoa_q33i \ + > pine_creek_backup.sql +``` + +> **Finding a tenant's schema name:** +> ```bash +> docker compose exec -T postgres psql -U hoafinance -d hoafinance \ +> -c "SELECT name, schema_name FROM shared.organizations WHERE status = 'active';" +> ``` + +--- + +## Restore a Backup into the Staged Environment + +### 1. Transfer the backup to the staging server + +```bash +scp hoafinance_backup.dump user@staging-server:/opt/hoa-ledgeriq/ +``` + +### 2. Ensure the stack is running + +```bash +cd /opt/hoa-ledgeriq +docker compose up -d +``` + +### 3. Drop and recreate the database (clean slate) + +```bash +# Connect to postgres and reset the database +docker compose exec -T postgres psql -U hoafinance -d postgres -c " + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = 'hoafinance' AND pid <> pg_backend_pid(); +" +docker compose exec -T postgres dropdb -U hoafinance hoafinance +docker compose exec -T postgres createdb -U hoafinance hoafinance +``` + +### 4a. Restore from custom-format dump + +```bash +# Copy the dump into the container +docker compose cp hoafinance_backup.dump postgres:/tmp/hoafinance_backup.dump + +# Restore +docker compose exec -T postgres pg_restore \ + -U hoafinance \ + -d hoafinance \ + --no-owner \ + --no-privileges \ + /tmp/hoafinance_backup.dump +``` + +### 4b. Restore from plain SQL dump + +```bash +docker compose exec -T postgres psql \ + -U hoafinance \ + -d hoafinance \ + < hoafinance_backup.sql +``` + +### 5. Restart the backend + +After restoring, restart the backend so NestJS re-establishes its connection +pool and picks up the restored schemas: + +```bash +docker compose restart backend +``` + +--- + +## Running Migrations on the Staged Environment + +Migrations live in `db/migrations/` and are numbered sequentially. After +restoring an older backup, you may need to apply newer migrations. + +Check which migrations exist: + +```bash +ls -la db/migrations/ +``` + +Apply them in order: + +```bash +# Run all migrations sequentially +for f in db/migrations/*.sql; do + echo "Applying $f ..." + docker compose exec -T postgres psql \ + -U hoafinance \ + -d hoafinance \ + < "$f" +done +``` + +Or apply a specific migration: + +```bash +docker compose exec -T postgres psql \ + -U hoafinance \ + -d hoafinance \ + < db/migrations/010-health-scores.sql +``` + +> **Note:** Migrations are idempotent where possible (`IF NOT EXISTS`, +> `DO $$ ... $$` blocks), so re-running one that has already been applied +> is generally safe. + +--- + +## Verifying the Deployment + +### Quick health checks + +```bash +# Backend is responding +curl -s http://localhost/api/auth/login | head -c 100 + +# Database is accessible +docker compose exec -T postgres psql -U hoafinance -d hoafinance \ + -c "SELECT count(*) AS tenants FROM shared.organizations WHERE status = 'active';" + +# Redis is working +docker compose exec -T redis redis-cli ping +``` + +### Full smoke test + +1. Open `http://` in a browser +2. Log in with a known account +3. Navigate to Dashboard — verify health scores load +4. Navigate to Capital Planning — verify Kanban columns render +5. Navigate to Projects — verify project list loads +6. Check the Settings page — version should read **2026.3.2 (beta)** + +### View logs + +```bash +docker compose logs -f # all services +docker compose logs -f backend # backend only +docker compose logs -f postgres # database only +``` + +--- + +## Environment Variable Reference + +| Variable | Required | Description | +|-------------------|----------|----------------------------------------------------| +| `POSTGRES_USER` | Yes | PostgreSQL username | +| `POSTGRES_PASSWORD`| Yes | PostgreSQL password (**change from default**) | +| `POSTGRES_DB` | Yes | Database name | +| `DATABASE_URL` | Yes | Full connection string for the backend | +| `REDIS_URL` | Yes | Redis connection string | +| `JWT_SECRET` | Yes | Secret for signing JWT tokens (**change from default**) | +| `NODE_ENV` | Yes | `development` or `production` | +| `AI_API_URL` | Yes | OpenAI-compatible inference endpoint | +| `AI_API_KEY` | Yes | API key for AI provider (Nvidia) | +| `AI_MODEL` | Yes | Model identifier for AI calls | +| `AI_DEBUG` | No | Set `true` to log raw AI prompts/responses | + +--- + +## Architecture Overview + +``` + ┌─────────────┐ + Browser ────────► │ nginx :80 │ + └──────┬──────┘ + ┌────────┴────────┐ + ▼ ▼ + ┌──────────────┐ ┌──────────────┐ + │ backend :3000│ │frontend :5173│ + │ (NestJS) │ │ (Vite/React) │ + └──────┬───────┘ └──────────────┘ + ┌────┴────┐ + ▼ ▼ + ┌────────────┐ ┌───────────┐ + │postgres:5432│ │redis :6379│ + │ (PG 15) │ │ (Redis 7) │ + └────────────┘ └───────────┘ +``` + +**Multi-tenant isolation:** Each HOA organization gets its own PostgreSQL +schema (e.g., `tenant_pine_creek_hoa_q33i`). The `shared` schema holds +cross-tenant tables (users, organizations, market rates). Tenant context +is resolved from the JWT token on every API request.