# Changemaker Control Panel (CCP) — Development Plan A multi-tenant management system for provisioning, monitoring, and operating multiple Changemaker Lite instances from a single dashboard. --- ## Vision CCP replaces manual `git clone` / `docker compose up` / `.env` editing with a web UI that can: - One-click provision a new Changemaker Lite instance (database, containers, config, tunnel) - Monitor instance health across the fleet - Start/stop/restart instances and individual services - Back up and restore instance data - Maintain a full audit trail of operator actions - Manage Pangolin tunnels for production exposure --- ## Architecture ``` ┌─────────────────────────┐ │ CCP Admin GUI (5100) │ React + Vite + Ant Design │ Dark theme, SPA │ Zustand auth store └────────────┬─────────────┘ │ /api/* proxy ┌────────────▼─────────────┐ │ CCP API (5000) │ Express + TypeScript │ JWT auth, RBAC │ Prisma ORM → PostgreSQL │ Docker socket access │ Winston logger └────────────┬─────────────┘ │ ┌───────────┬───────────┼───────────┬──────────┐ ▼ ▼ ▼ ▼ ▼ ccp-postgres ccp-redis Docker Socket /opt/ccp/ /var/backups/ (port 5480) (port 6399) (.sock) instances ccp-instances ``` ### Stack | Layer | Technology | Notes | |-------|-----------|-------| | API | Express 4, TypeScript 5, Node 20 | `express-async-errors` for async route handling | | ORM | Prisma 6 + PostgreSQL 16 | 10 models, mapped table names | | Auth | JWT (jsonwebtoken) + bcryptjs | 15min access / 7d refresh, atomic rotation | | Encryption | AES-256-GCM (Node crypto) | Secrets at rest in `encrypted_secrets` column | | Frontend | React 19, Vite 6, Ant Design 5 | Dark theme, Zustand state, axios interceptors | | Docker | Docker CLI + socket API | compose up/down/ps/exec/logs via shell + HTTP socket | | Templates | Handlebars | .env, docker-compose.yml, nginx configs rendered per-instance | | Logging | Winston | JSON in production, colorized in development | | Config | Zod schema validation | Fails fast on startup with clear error messages | ### Key Design Decisions 1. **Docker CLI over Docker SDK** — Shell out to `docker compose` commands rather than using dockerode. Simpler, matches what operators would run manually, and `docker compose` handles all the orchestration logic. 2. **Shared PostgreSQL** — All CCP data in one database; each CML instance gets its own isolated PostgreSQL container with unique ports and passwords. 3. **Port Range Allocation** — Four non-overlapping port ranges prevent conflicts: - API: 14000-14999 - Admin: 13000-13999 - PostgreSQL: 15400-15499 - Nginx: 10000-10999 4. **Async Provisioning** — Instance creation returns immediately; provisioning runs fire-and-forget with progress tracked via `status`/`statusMessage` fields. Frontend polls for updates. 5. **Template-Based Config** — Handlebars templates render per-instance docker-compose.yml, .env, nginx configs, and Pangolin resources. This avoids complex string manipulation and keeps configs readable. --- ## Database Schema (Prisma) ``` CcpUser ──< CcpRefreshToken │ ├──< AuditLog │ Instance ──< PortAllocation │ ├──< HealthCheck ├──< Backup └──< AuditLog CcpSetting (key-value) ``` ### Models | Model | Purpose | Key Fields | |-------|---------|------------| | **CcpUser** | Control panel operators | email, password (bcrypt), role (SUPER_ADMIN/OPERATOR/VIEWER) | | **CcpRefreshToken** | JWT refresh token storage | token (SHA-256 hash), expiresAt | | **Instance** | Managed CML instance | slug, domain, basePath, composeProject, status, portConfig (JSON), encryptedSecrets, feature flags | | **PortAllocation** | Port registry | port (unique), service name, instanceId | | **HealthCheck** | Periodic health snapshots | status (HEALTHY/DEGRADED/UNHEALTHY/UNKNOWN), serviceStatus (JSON), totalServices, healthyServices, responseTimeMs | | **Backup** | Backup records | status (PENDING→IN_PROGRESS→COMPLETED/FAILED), archivePath, sizeBytes, manifest (JSON) | | **AuditLog** | Action trail | action (18 types), userId, instanceId, details (JSON), ipAddress | | **CcpSetting** | Global key-value config | key (PK), value (JSON) | ### Audit Actions (18 types) ``` INSTANCE_CREATE, INSTANCE_UPDATE, INSTANCE_DELETE, INSTANCE_START, INSTANCE_STOP, INSTANCE_RESTART, INSTANCE_UPGRADE, BACKUP_CREATE, BACKUP_DELETE, PANGOLIN_SETUP, PANGOLIN_SYNC, USER_LOGIN, USER_CREATE, USER_UPDATE, USER_DELETE, SETTINGS_UPDATE ``` --- ## API Endpoints ### Authentication | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | `/api/auth/login` | Public | Login, returns JWT pair | | POST | `/api/auth/refresh` | Public | Rotate refresh token | | POST | `/api/auth/logout` | Public | Revoke refresh token | | GET | `/api/auth/me` | Authenticated | Current user profile | ### Instances — CRUD | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/instances` | Authenticated | List all instances | | GET | `/api/instances/:id` | Authenticated | Instance detail (includes health + backups) | | POST | `/api/instances` | SUPER_ADMIN, OPERATOR | Create instance (triggers async provisioning) | | PUT | `/api/instances/:id` | SUPER_ADMIN, OPERATOR | Update instance config | | DELETE | `/api/instances/:id` | SUPER_ADMIN | Delete instance (stops containers, removes files) | | GET | `/api/instances/:id/secrets` | SUPER_ADMIN | Decrypt and return instance secrets | ### Instances — Lifecycle | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | `/api/instances/:id/provision` | SUPER_ADMIN, OPERATOR | Retry provisioning | | POST | `/api/instances/:id/start` | SUPER_ADMIN, OPERATOR | Start all containers | | POST | `/api/instances/:id/stop` | SUPER_ADMIN, OPERATOR | Stop all containers | | POST | `/api/instances/:id/restart` | SUPER_ADMIN, OPERATOR | Restart (optionally `?service=api`) | ### Instances — Services & Logs | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/instances/:id/services` | Authenticated | Container status via `docker compose ps` | | GET | `/api/instances/:id/logs` | Authenticated | Logs (`?service=api&tail=200&since=1h`) | ### Instances — Health | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | `/api/instances/:id/health-check` | SUPER_ADMIN, OPERATOR | Trigger manual health check | | GET | `/api/instances/:id/health-history` | Authenticated | Paginated health check history | ### Instances — Backups | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | `/api/instances/:id/backup` | SUPER_ADMIN, OPERATOR | Create backup (async) | | GET | `/api/instances/:id/backups` | Authenticated | List instance backups | ### Backups (cross-instance) | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/backups` | Authenticated | List all backups (`?instanceId=...&page=1&limit=50`) | | DELETE | `/api/backups/:id` | SUPER_ADMIN | Delete backup (file + record) | | GET | `/api/backups/:id/download` | SUPER_ADMIN | Stream backup archive | ### Health (CCP-level) | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/health` | Public | CCP API health check | | GET | `/api/health/overview` | Authenticated | All instances with latest health | ### Audit | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/audit` | Authenticated | Filtered, paginated audit log (`?action=...&instanceId=...&userId=...&from=...&to=...`) | ### Settings | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | `/api/settings` | Authenticated | All settings as key-value map | | PUT | `/api/settings/:key` | SUPER_ADMIN | Upsert a setting value | --- ## Frontend Pages | Route | Page | Description | |-------|------|-------------| | `/login` | LoginPage | Email/password form | | `/app` | DashboardPage | Stats (total, running, healthy, degraded, stopped, errors) + instance cards | | `/app/instances` | InstanceListPage | Table view with search/filter | | `/app/instances/new` | CreateWizardPage | 5-step wizard (info, features, email, tunnel, review) | | `/app/instances/:id` | InstanceDetailPage | Tabs: Overview, Services, Logs, Backups, Tunnel | | `/app/backups` | BackupsPage | Cross-instance backup list with stats | | `/app/audit` | AuditLogPage | Filterable audit log + detail drawer | | `/app/settings` | SettingsPage | Port ranges, Pangolin config, defaults | ### Sidebar Navigation 1. Dashboard (home) 2. Instances (list) 3. Backups (cross-instance) 4. Audit Log (activity trail) 5. Settings (CCP config) --- ## Provisioning Flow When `POST /api/instances` is called, the system: ``` 1. Validate uniqueness (slug + domain) 2. Allocate 4 ports from ranges 3. Generate 14 secrets (passwords, JWT keys, encryption keys) 4. Create Instance record (status: PROVISIONING) 5. [async] Create directory: /opt/ccp/instances/{slug}/changemaker.lite/ 6. [async] rsync CML source (excluding node_modules, .git, .env, .claude) 7. [async] Decrypt secrets → build Handlebars context 8. [async] Render 7 templates: docker-compose.yml, .env, nginx configs, Pangolin, Prometheus 9. [async] Copy static files (nginx.conf) 10. [async] docker compose pull (non-fatal if images cached) 11. [async] docker compose build 12. [async] Start infrastructure: v2-postgres + redis-changemaker 13. [async] Wait for healthy (Docker healthcheck polling) 14. [async] Start API → run prisma migrate deploy → prisma db seed 15. [async] docker compose up (all services) 16. [async] Wait for HTTP health (localhost:{api_port}/api/health) 17. [async] Set status: RUNNING ``` Frontend polls `GET /api/instances/:id` every 3 seconds during provisioning to show progress. --- ## Health Check System ### How It Works 1. **Scheduler** starts on API boot (default: every 5 minutes, configurable via `HEALTH_CHECK_INTERVAL_MS`) 2. For each RUNNING instance, runs `docker compose ps --format json` 3. Parses container states and health check results 4. Determines overall status: - **HEALTHY** — all containers running, all health checks passing - **DEGRADED** — some containers running but not all, or some health checks failing - **UNHEALTHY** — majority of containers down or failing health checks - **UNKNOWN** — no containers found or compose project doesn't exist 5. Stores `HealthCheck` record with per-service status JSON, response time 6. Updates `instance.lastHealthCheck` timestamp ### Manual Trigger `POST /api/instances/:id/health-check` runs an immediate check (SUPER_ADMIN/OPERATOR only). --- ## Backup System ### What Gets Backed Up 1. **PostgreSQL dump** — `pg_dump` inside the instance's v2-postgres container 2. **Uploads archive** — tar.gz of the uploads directory (if it exists) 3. **Manifest** — JSON file with file names, sizes, SHA-256 hashes ### Backup Flow ``` 1. Validate instance is RUNNING 2. Create Backup record (status: PENDING) 3. [async] Set status: IN_PROGRESS 4. [async] mkdir /var/backups/ccp-instances/{slug}/{timestamp}/ 5. [async] docker compose exec v2-postgres pg_dump → v2-postgres.sql → gzip 6. [async] tar -czf uploads.tar.gz (if uploads/ exists) 7. [async] Write manifest.json with file inventory + SHA-256 hashes 8. [async] tar -czf final archive → /var/backups/ccp-instances/{slug}/backup-{slug}-{timestamp}.tar.gz 9. [async] Cleanup temp directory 10. [async] Update Backup record (COMPLETED, archivePath, sizeBytes, manifest) 11. [async] Write audit log ``` ### Retention `cleanupOldBackups(retentionDays)` deletes archives + records older than the configured retention period (default: 30 days). --- ## Phased Implementation ### Phase 1: Foundation (COMPLETE) **Goal:** Skeleton that boots — database, auth, project structure. **Delivered:** - Prisma schema with all 10 models (User, Instance, HealthCheck, Backup, AuditLog, etc.) - JWT authentication (access + refresh tokens with atomic rotation) - Role-based access control (SUPER_ADMIN, OPERATOR, VIEWER) - Zod-validated environment configuration - AES-256-GCM encryption for instance secrets - React admin shell with dark theme, sidebar navigation, protected routes - Zustand auth store with token persistence + refresh interceptor - Login page, Dashboard placeholder, Settings page - Docker Compose orchestration (PostgreSQL, Redis, API, Admin) - Placeholder pages wired for Audit Log, Backups ### Phase 2: Docker Lifecycle (COMPLETE) **Goal:** Create and manage running CML instances. **Delivered:** - Instance CRUD with slug/domain uniqueness validation - Port allocation across 4 ranges (API, Admin, PostgreSQL, Nginx) - Secret generation (14 secrets: postgres, redis, JWT, encryption, admin passwords) - Handlebars template engine rendering 7 config files per instance - 13-step async provisioning (copy source → render config → pull → build → migrate → seed → start) - Lifecycle operations: start, stop, restart (whole stack or individual service) - Container status via `docker compose ps --format json` - Log viewing via `docker compose logs` with service/tail/since filters - 5-step Create Instance wizard (basic info → features → email → tunnel → review) - Instance detail page with tabs (Overview, Services, Logs, Backups, Tunnel) - Service health grid with per-container restart/log-view actions - Provisioning progress indicator (polls every 3s) - Audit logging on all lifecycle operations (7 action types) ### Phase 3: Observability + Backups (COMPLETE) **Goal:** Visibility into instance health, operator activity trail, and data protection. **Delivered:** #### Part A — Audit Log - Audit service with filtered queries (action, instance, user, date range) + pagination - `GET /api/audit` endpoint with query parameter filtering - IP address capture on all audit log entries (existing + new) - `USER_LOGIN` audit event on successful authentication - `SETTINGS_UPDATE` audit event on settings changes - Full AuditLogPage: filterable table, action-colored tags, detail drawer with JSON inspector, server-side pagination, 30s auto-refresh toggle #### Part B — Health Checks - Health service with `checkInstanceHealth()` analyzing Docker container states - Overall status determination (HEALTHY/DEGRADED/UNHEALTHY/UNKNOWN) from per-container state - Scheduled health checker (default 5-minute interval, configurable, 0 to disable) - `POST /api/instances/:id/health-check` for manual checks - `GET /api/instances/:id/health-history` for paginated history - Health card in Instance Detail overview with "Check Now" button + history table - Dashboard stat cards: Healthy and Degraded instance counts from `/api/health/overview` #### Part C — Backups - Backup service: pg_dump via Docker exec, uploads tar.gz, SHA-256 manifest, final archive - Async backup creation with PENDING → IN_PROGRESS → COMPLETED/FAILED progression - `POST /api/instances/:id/backup` — create backup - `GET /api/instances/:id/backups` — instance-scoped backup list - `GET /api/backups` — cross-instance backup list with pagination - `DELETE /api/backups/:id` — delete backup (file + DB record) - `GET /api/backups/:id/download` — stream backup archive for download - BackupsPage: cross-instance table, instance filter, stats (count/size/last), "Backup All Running" - Enhanced Instance Detail backups tab: create/download/delete actions, status polling - Backup storage volume mount in docker-compose.yml - Old backup cleanup utility (configurable retention days) ### Phase 4: Pangolin Integration (PLANNED) **Goal:** Automated tunnel setup for exposing instances to the internet. **Scope:** - Pangolin API client (site creation, resource management, Newt credentials) - Automated tunnel setup endpoint (`POST /api/instances/:id/setup-tunnel`) - Per-instance Newt container management (start/stop with stored credentials) - Resource sync for all instance subdomains (app, api, media, docs, etc.) - Tunnel status monitoring in Instance Detail tunnel tab - Bulk tunnel setup for fleet-wide deployment ### Phase 5: Upgrades + Git Integration (PLANNED) **Goal:** Rolling upgrades and version management. **Scope:** - Git pull + branch checkout for instance source code - Database migration execution (prisma migrate deploy) - Docker image rebuild + rolling restart - Upgrade progress tracking (similar to provisioning) - Rollback capability (pre-upgrade backup + restore) - Version display on instance cards and detail pages - `INSTANCE_UPGRADE` audit event ### Phase 6: User Management + RBAC (PLANNED) **Goal:** Multi-operator support with granular permissions. **Scope:** - User CRUD pages (create, edit, delete operators) - Role assignment and management - Per-instance access control (operator can only manage assigned instances) - Invitation flow (email invite with temporary password) - Password change and reset functionality - Session management (view/revoke active sessions) ### Phase 7: Monitoring Dashboard (PLANNED) **Goal:** Fleet-wide observability with trends and alerting. **Scope:** - Health trend charts (uptime over time) - Resource utilization (CPU, memory, disk via Docker stats) - Alert rules (instance down > N minutes, disk usage > threshold) - Notification channels (email, webhook) - Backup compliance monitoring (last backup age alerts) - Fleet summary dashboard with at-a-glance status ### Phase 8: Instance Configuration UI (PLANNED) **Goal:** Edit instance configuration without SSH. **Scope:** - Feature flag toggles (media, listmonk, gancio, monitoring) - SMTP configuration management - .env variable editor (safe subset) - Docker Compose service scaling - Configuration diff preview before applying - Auto-restart on config change --- ## Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `NODE_ENV` | `development` | Environment mode | | `PORT` | `5000` | API server port | | `DATABASE_URL` | — | PostgreSQL connection string | | `REDIS_URL` | `redis://localhost:6399` | Redis connection string | | `JWT_ACCESS_SECRET` | — | JWT signing key (min 32 chars) | | `JWT_REFRESH_SECRET` | — | Refresh token signing key (min 32 chars) | | `JWT_ACCESS_EXPIRES_IN` | `15m` | Access token lifetime | | `JWT_REFRESH_EXPIRES_IN` | `7d` | Refresh token lifetime | | `ENCRYPTION_KEY` | — | AES-256 key for secrets at rest (min 32 hex chars) | | `INITIAL_ADMIN_EMAIL` | `admin@example.com` | Bootstrap admin email | | `INITIAL_ADMIN_PASSWORD` | `ChangeMe2025!!` | Bootstrap admin password (min 12 chars) | | `CORS_ORIGINS` | `http://localhost:5100` | Allowed origins (comma-separated) | | `INSTANCES_BASE_PATH` | `/opt/ccp/instances` | Where instance directories live | | `CML_SOURCE_PATH` | `/home/bunker-admin/changemaker.lite` | CML source to clone from | | `CML_GIT_REPO` | — | Git repo URL (if cloning remotely) | | `CML_GIT_BRANCH` | `v2` | Default branch for new instances | | `PORT_RANGE_API_START` | `14000` | API port range start | | `PORT_RANGE_API_END` | `14999` | API port range end | | `PORT_RANGE_ADMIN_START` | `13000` | Admin port range start | | `PORT_RANGE_ADMIN_END` | `13999` | Admin port range end | | `PORT_RANGE_POSTGRES_START` | `15400` | PostgreSQL port range start | | `PORT_RANGE_POSTGRES_END` | `15499` | PostgreSQL port range end | | `PORT_RANGE_NGINX_START` | `10000` | Nginx port range start | | `PORT_RANGE_NGINX_END` | `10999` | Nginx port range end | | `PANGOLIN_API_URL` | — | Pangolin API base URL | | `PANGOLIN_API_KEY` | — | Pangolin API key | | `PANGOLIN_ORG_ID` | — | Pangolin organization ID | | `HEALTH_CHECK_INTERVAL_MS` | `300000` | Health check interval (0 to disable) | | `BACKUP_STORAGE_PATH` | `/var/backups/ccp-instances` | Backup archive storage directory | | `BACKUP_RETENTION_DAYS` | `30` | Auto-cleanup threshold | --- ## Docker Services ```yaml services: ccp-postgres: # PostgreSQL 16 Alpine — CCP database (port 5480) ccp-redis: # Redis 7 Alpine — rate limiting, caching (port 6399) ccp-api: # Node 20 + Docker CLI — API server (port 5000) ccp-admin: # Nginx + React SPA — admin GUI (port 5100) ``` ### Volume Mounts (ccp-api) | Host | Container | Mode | Purpose | |------|-----------|------|---------| | `./api` | `/app` | rw | Source code (dev) | | `./templates` | `/app/templates` | ro | Handlebars templates | | `/var/run/docker.sock` | `/var/run/docker.sock` | — | Docker CLI access | | `$INSTANCES_BASE_PATH` | Same | rw | Instance directories | | `$CML_SOURCE_PATH` | Same | ro | CML source for provisioning | | `$BACKUP_STORAGE_PATH` | Same | rw | Backup archives | --- ## File Inventory ### API (`api/`) ``` src/ ├── server.ts # Express app + route mounting + health scheduler start ├── config/ │ ├── env.ts # Zod env validation (30+ vars) │ └── redis.ts # Redis client config ├── middleware/ │ ├── auth.ts # authenticate + requireRole middleware │ ├── error-handler.ts # AppError class + global error handler │ └── validate.ts # Zod request body validation ├── modules/ │ ├── auth/ │ │ ├── auth.routes.ts # POST login/refresh/logout, GET /me │ │ ├── auth.schemas.ts # Zod schemas for auth payloads │ │ └── auth.service.ts # JWT generation, bcrypt verify, token rotation │ ├── instances/ │ │ ├── instances.routes.ts # CRUD + lifecycle + services + logs + health + backups │ │ ├── instances.schemas.ts # Zod create/update validation │ │ ├── instances.service.ts # Business logic + audit logging with IP capture │ │ └── provisioner.ts # 13-step async provisioning orchestration │ ├── audit/ │ │ ├── audit.routes.ts # GET /api/audit with filters + pagination │ │ └── audit.service.ts # Prisma query builder for audit logs │ ├── backups/ │ │ └── backup.routes.ts # GET list, DELETE, GET download │ ├── health/ │ │ └── health.routes.ts # Public health + authenticated overview │ └── settings/ │ └── settings.routes.ts # GET all, PUT :key (+ audit logging) ├── services/ │ ├── docker.service.ts # Docker CLI wrapper (ps, exec, logs, up, down, etc.) │ ├── health.service.ts # Health check logic + 5-minute scheduler │ ├── backup.service.ts # pg_dump + tar + manifest + cleanup │ ├── port-allocator.ts # Port range management │ ├── secret-generator.ts # CML-compatible credential generation │ └── template-engine.ts # Handlebars rendering for 7 config files └── utils/ ├── encryption.ts # AES-256-GCM encrypt/decrypt └── logger.ts # Winston config ``` ### Admin (`admin/`) ``` src/ ├── App.tsx # Route definitions + dark theme config ├── main.tsx # React entry ├── components/ │ ├── AppLayout.tsx # Sidebar nav + header + user dropdown │ ├── InstanceCard.tsx # Card with status, health bar, features │ ├── ServiceHealthGrid.tsx # Container state table with actions │ ├── LogViewer.tsx # Log display with service filter │ └── ProtectedRoute.tsx # Auth guard wrapper ├── pages/ │ ├── LoginPage.tsx # Auth form │ ├── DashboardPage.tsx # Stats + health overview + instance cards │ ├── InstanceListPage.tsx # Instance table │ ├── CreateWizardPage.tsx # 5-step provisioning wizard │ ├── InstanceDetailPage.tsx # Tabbed detail (overview/services/logs/backups/tunnel) │ ├── BackupsPage.tsx # Cross-instance backup manager │ ├── AuditLogPage.tsx # Filterable audit trail │ └── SettingsPage.tsx # CCP configuration ├── stores/ │ └── auth.store.ts # Zustand auth + localStorage persistence ├── lib/ │ └── api.ts # Axios + auth interceptors + token refresh └── types/ └── api.ts # TypeScript interfaces for all API models ``` ### Templates (`templates/`) ``` docker-compose.yml.hbs # Full CML docker-compose with ports/secrets/features env.hbs # Instance .env file nginx/ ├── nginx.conf # Global nginx config (static copy) └── conf.d/ ├── default.conf.hbs # Subdomain routing ├── api.conf.hbs # API reverse proxy └── services.conf.hbs # Service proxies configs/ ├── pangolin/resources.yml.hbs # Tunnel resource definitions └── prometheus/prometheus.yml.hbs # Monitoring scrape targets ``` --- ## Quick Start ```bash # 1. Clone and enter the CCP directory cd changemaker.lite/changemaker-control-panel # 2. Copy environment file cp .env.example .env # Edit .env: set strong passwords for JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, ENCRYPTION_KEY # 3. Start CCP docker compose up -d # 4. Run migrations + seed docker compose exec ccp-api npx prisma migrate deploy docker compose exec ccp-api npx prisma db seed # 5. Access admin GUI open http://localhost:5100 # Login with INITIAL_ADMIN_EMAIL / INITIAL_ADMIN_PASSWORD from .env ``` --- ## Development ```bash # API development (hot reload) cd api && npm install && npx tsx src/server.ts # Admin development (Vite dev server) cd admin && npm install && npm run dev # Type checking cd api && npx tsc --noEmit cd admin && npx tsc --noEmit # Database operations cd api && npx prisma migrate dev # Create/apply migrations cd api && npx prisma studio # Browse database GUI cd api && npx prisma db seed # Re-seed admin user ```