Add standalone production deployment package
Build and Push Docker Image / build (push) Successful in 1m46s
Details
Build and Push Docker Image / build (push) Successful in 1m46s
Details
- docker-compose.yml: Standalone compose with Traefik, Supabase, portal - init.sql: Combined database schema + all 16 migrations - kong.yml.template: Kong config with API key placeholders - setup.sh: Auto-generates secrets (JWT, passwords, API keys) - .env.example: Comprehensive environment template - README.md: Complete deployment guide No source code cloning required - just copy files and run setup.sh Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bcd5b955a3
commit
3187f5babb
|
|
@ -0,0 +1,129 @@
|
||||||
|
# ============================================
|
||||||
|
# Monaco USA Portal - Production Configuration
|
||||||
|
# ============================================
|
||||||
|
# Copy this file to .env and configure the values below.
|
||||||
|
# Then run ./setup.sh to generate secrets and kong.yml.
|
||||||
|
#
|
||||||
|
# Variables marked [AUTO-GENERATED] will be created by setup.sh
|
||||||
|
# if left empty or containing placeholder text.
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# DOMAIN CONFIGURATION (Required)
|
||||||
|
# ============================================
|
||||||
|
# Your domain name (without https://)
|
||||||
|
DOMAIN=portal.monacousa.org
|
||||||
|
|
||||||
|
# Email for Let's Encrypt SSL certificates
|
||||||
|
ACME_EMAIL=admin@monacousa.org
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# DATABASE CONFIGURATION
|
||||||
|
# ============================================
|
||||||
|
# PostgreSQL settings
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_DB=postgres
|
||||||
|
|
||||||
|
# [AUTO-GENERATED] Database password - leave as placeholder for auto-generation
|
||||||
|
# To generate manually: openssl rand -base64 32
|
||||||
|
POSTGRES_PASSWORD=CHANGE_ME_RUN_SETUP_SH
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# JWT CONFIGURATION
|
||||||
|
# ============================================
|
||||||
|
# [AUTO-GENERATED] JWT secret for Supabase auth - leave as placeholder
|
||||||
|
# To generate manually: openssl rand -base64 32
|
||||||
|
JWT_SECRET=CHANGE_ME_RUN_SETUP_SH
|
||||||
|
|
||||||
|
# JWT token expiry in seconds (default: 1 hour)
|
||||||
|
JWT_EXPIRY=3600
|
||||||
|
|
||||||
|
# [AUTO-GENERATED] Anonymous API key - leave as placeholder
|
||||||
|
# This is a JWT signed with JWT_SECRET with role=anon
|
||||||
|
ANON_KEY=your-anon-key-will-be-generated
|
||||||
|
|
||||||
|
# [AUTO-GENERATED] Service role API key - leave as placeholder
|
||||||
|
# This is a JWT signed with JWT_SECRET with role=service_role
|
||||||
|
SERVICE_ROLE_KEY=your-service-role-key-will-be-generated
|
||||||
|
|
||||||
|
# These are aliases used by the portal app
|
||||||
|
PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# REALTIME CONFIGURATION
|
||||||
|
# ============================================
|
||||||
|
# [AUTO-GENERATED] Secret key for realtime service
|
||||||
|
# To generate manually: openssl rand -base64 64
|
||||||
|
SECRET_KEY_BASE=generate-a-64-char-secret-key-run-setup-sh
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# EMAIL CONFIGURATION (Optional but Recommended)
|
||||||
|
# ============================================
|
||||||
|
# SMTP settings for sending emails (password resets, invitations, etc.)
|
||||||
|
# Leave empty to disable email functionality (users won't receive confirmation emails)
|
||||||
|
SMTP_HOST=smtp.gmail.com
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASS=
|
||||||
|
SMTP_ADMIN_EMAIL=noreply@monacousa.org
|
||||||
|
SMTP_SENDER_NAME=Monaco USA
|
||||||
|
|
||||||
|
# Set to true to auto-confirm emails (not recommended for production)
|
||||||
|
ENABLE_EMAIL_AUTOCONFIRM=false
|
||||||
|
|
||||||
|
# Rate limit for emails sent per hour
|
||||||
|
RATE_LIMIT_EMAIL_SENT=100
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# PORTAL APPLICATION CONFIGURATION
|
||||||
|
# ============================================
|
||||||
|
# Docker image for the portal app
|
||||||
|
# Change this to use a different registry or version
|
||||||
|
PORTAL_IMAGE=code.letsbe.solutions/letsbe/monacousa-portal:latest
|
||||||
|
|
||||||
|
# Maximum file upload size in bytes (default: 50MB)
|
||||||
|
BODY_SIZE_LIMIT=52428800
|
||||||
|
|
||||||
|
# Disable public signup (true = only admin can create accounts)
|
||||||
|
DISABLE_SIGNUP=false
|
||||||
|
|
||||||
|
# Additional redirect URLs for OAuth (comma-separated)
|
||||||
|
ADDITIONAL_REDIRECT_URLS=
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# POSTGREST CONFIGURATION
|
||||||
|
# ============================================
|
||||||
|
# Database schemas exposed via REST API
|
||||||
|
PGRST_DB_SCHEMAS=public,storage,graphql_public
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# SECURITY - DASHBOARD ACCESS (Optional)
|
||||||
|
# ============================================
|
||||||
|
# Basic auth for Traefik dashboard (format: user:password-hash)
|
||||||
|
# Generate with: htpasswd -nB admin
|
||||||
|
# Example: admin:$apr1$xyz...
|
||||||
|
TRAEFIK_DASHBOARD_AUTH=
|
||||||
|
|
||||||
|
# Basic auth for Supabase Studio (format: user:password-hash)
|
||||||
|
# Generate with: htpasswd -nB admin
|
||||||
|
STUDIO_AUTH=
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# NOTES
|
||||||
|
# ============================================
|
||||||
|
# 1. After configuring this file, run: ./setup.sh
|
||||||
|
# This will:
|
||||||
|
# - Generate any missing secrets
|
||||||
|
# - Create kong.yml from template
|
||||||
|
# - Validate your configuration
|
||||||
|
#
|
||||||
|
# 2. Start the services: docker compose up -d
|
||||||
|
#
|
||||||
|
# 3. Check status: docker compose ps
|
||||||
|
#
|
||||||
|
# 4. View logs: docker compose logs -f
|
||||||
|
#
|
||||||
|
# 5. First visit to https://DOMAIN will redirect to /setup
|
||||||
|
# to create the initial admin account.
|
||||||
|
# ============================================
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
# Monaco USA Portal - Standalone Production Deployment
|
||||||
|
|
||||||
|
This is a standalone deployment package for the Monaco USA Portal. No source code cloning required.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Linux server (Ubuntu 22.04+ recommended)
|
||||||
|
- Docker Engine 24.0+
|
||||||
|
- Docker Compose v2.20+
|
||||||
|
- Domain name with DNS pointing to your server
|
||||||
|
- Ports 80 and 443 open
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Download the deployment files
|
||||||
|
|
||||||
|
Create a directory and download the deployment files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /opt/monacousa
|
||||||
|
cd /opt/monacousa
|
||||||
|
|
||||||
|
# Download files from your deployment source
|
||||||
|
# Example: scp, git clone, or direct download
|
||||||
|
```
|
||||||
|
|
||||||
|
You need these files:
|
||||||
|
- `docker-compose.yml`
|
||||||
|
- `.env.example`
|
||||||
|
- `init.sql`
|
||||||
|
- `kong.yml.template`
|
||||||
|
- `setup.sh`
|
||||||
|
|
||||||
|
### 2. Configure environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy the example environment file
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit with your settings
|
||||||
|
nano .env
|
||||||
|
```
|
||||||
|
|
||||||
|
At minimum, configure:
|
||||||
|
- `DOMAIN` - Your domain name (e.g., `portal.monacousa.org`)
|
||||||
|
- `ACME_EMAIL` - Email for SSL certificates
|
||||||
|
- SMTP settings (optional but recommended for emails)
|
||||||
|
|
||||||
|
### 3. Run setup script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make setup script executable
|
||||||
|
chmod +x setup.sh
|
||||||
|
|
||||||
|
# Run setup - this generates secrets and kong.yml
|
||||||
|
./setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The setup script will:
|
||||||
|
- Generate secure random passwords and JWT tokens
|
||||||
|
- Create `kong.yml` from the template with your API keys
|
||||||
|
- Validate your configuration
|
||||||
|
|
||||||
|
### 4. Start the services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Verify deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check all containers are running
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# Check database initialization
|
||||||
|
docker compose logs db
|
||||||
|
|
||||||
|
# Check for any errors
|
||||||
|
docker compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Access the portal
|
||||||
|
|
||||||
|
Open `https://your-domain.com` in your browser. On first visit, you'll be redirected to `/setup` to create the initial admin account.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Internet
|
||||||
|
│
|
||||||
|
├─► :80/:443 ──► Traefik (SSL/Reverse Proxy)
|
||||||
|
│ │
|
||||||
|
│ ├─► portal.domain.com ──► Portal (SvelteKit)
|
||||||
|
│ ├─► api.domain.com ──► Kong ──► Auth/REST/Storage
|
||||||
|
│ └─► studio.domain.com ──► Studio (Dashboard)
|
||||||
|
│
|
||||||
|
Internal Network
|
||||||
|
│
|
||||||
|
├─► Kong API Gateway
|
||||||
|
│ ├─► Auth (GoTrue)
|
||||||
|
│ ├─► REST (PostgREST)
|
||||||
|
│ ├─► Storage API
|
||||||
|
│ └─► Realtime
|
||||||
|
│
|
||||||
|
└─► PostgreSQL Database
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files Description
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `docker-compose.yml` | All service definitions |
|
||||||
|
| `.env` | Your configuration (from .env.example) |
|
||||||
|
| `init.sql` | Database schema and migrations |
|
||||||
|
| `kong.yml.template` | API gateway config template |
|
||||||
|
| `kong.yml` | Generated API gateway config (created by setup.sh) |
|
||||||
|
| `setup.sh` | Setup script for secrets and validation |
|
||||||
|
|
||||||
|
## Management Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all services
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Stop all services
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# View specific service logs
|
||||||
|
docker compose logs -f portal
|
||||||
|
docker compose logs -f db
|
||||||
|
|
||||||
|
# Restart a specific service
|
||||||
|
docker compose restart portal
|
||||||
|
|
||||||
|
# Check resource usage
|
||||||
|
docker stats
|
||||||
|
|
||||||
|
# Enter database shell
|
||||||
|
docker compose exec db psql -U postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
To update the portal to a new version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull the latest image
|
||||||
|
docker compose pull portal
|
||||||
|
|
||||||
|
# Restart the portal service
|
||||||
|
docker compose up -d portal
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
### Database backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create backup
|
||||||
|
docker compose exec db pg_dump -U postgres postgres > backup_$(date +%Y%m%d).sql
|
||||||
|
|
||||||
|
# Restore backup
|
||||||
|
docker compose exec -T db psql -U postgres postgres < backup_YYYYMMDD.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full backup (including storage)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop services first for consistent backup
|
||||||
|
docker compose stop
|
||||||
|
|
||||||
|
# Backup volumes
|
||||||
|
docker run --rm -v monacousa_db-data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar czf /backup/db-data-backup.tar.gz -C /data .
|
||||||
|
|
||||||
|
docker run --rm -v monacousa_storage-data:/data -v $(pwd):/backup alpine \
|
||||||
|
tar czf /backup/storage-data-backup.tar.gz -C /data .
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Containers not starting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs for errors
|
||||||
|
docker compose logs
|
||||||
|
|
||||||
|
# Check if ports are in use
|
||||||
|
netstat -tlnp | grep -E ':(80|443)'
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSL certificate issues
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check Traefik logs
|
||||||
|
docker compose logs traefik
|
||||||
|
|
||||||
|
# Verify DNS is pointing to server
|
||||||
|
dig +short your-domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database connection errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check database is healthy
|
||||||
|
docker compose ps db
|
||||||
|
|
||||||
|
# Check database logs
|
||||||
|
docker compose logs db
|
||||||
|
|
||||||
|
# Verify database is accepting connections
|
||||||
|
docker compose exec db pg_isready -U postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 401 Unauthorized errors
|
||||||
|
|
||||||
|
This usually means the API keys don't match. Run setup again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./setup.sh
|
||||||
|
docker compose restart kong
|
||||||
|
```
|
||||||
|
|
||||||
|
### Portal not loading
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check portal logs
|
||||||
|
docker compose logs portal
|
||||||
|
|
||||||
|
# Verify kong is routing correctly
|
||||||
|
docker compose exec portal wget -qO- http://kong:8000/rest/v1/ || echo "Kong not reachable"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Recommendations
|
||||||
|
|
||||||
|
1. **Secure your .env file**
|
||||||
|
```bash
|
||||||
|
chmod 600 .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Enable dashboard authentication**
|
||||||
|
```bash
|
||||||
|
# Generate password hash
|
||||||
|
htpasswd -nB admin
|
||||||
|
# Add to .env as STUDIO_AUTH and TRAEFIK_DASHBOARD_AUTH
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Set up firewall**
|
||||||
|
```bash
|
||||||
|
ufw allow 80/tcp
|
||||||
|
ufw allow 443/tcp
|
||||||
|
ufw allow 22/tcp
|
||||||
|
ufw enable
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Regular updates**
|
||||||
|
- Keep Docker and host OS updated
|
||||||
|
- Regularly pull latest portal images
|
||||||
|
|
||||||
|
5. **Monitor logs**
|
||||||
|
- Set up log rotation (configured in docker-compose.yml)
|
||||||
|
- Consider centralized logging (ELK, Loki, etc.)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues and questions:
|
||||||
|
- Check logs: `docker compose logs -f`
|
||||||
|
- GitHub issues: [Project Repository]
|
||||||
|
- Email: support@monacousa.org
|
||||||
|
|
@ -0,0 +1,437 @@
|
||||||
|
# Monaco USA Portal - Standalone Production Docker Compose
|
||||||
|
# ========================================================
|
||||||
|
# This is a standalone deployment configuration.
|
||||||
|
# No source code cloning required - just this file and .env
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# 1. Copy this file and .env.example to your server
|
||||||
|
# 2. Copy .env.example to .env and configure
|
||||||
|
# 3. Run ./setup.sh to generate secrets and kong.yml
|
||||||
|
# 4. Run: docker compose up -d
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Docker and Docker Compose installed
|
||||||
|
# - Domain DNS pointing to server IP
|
||||||
|
# - Ports 80 and 443 open
|
||||||
|
|
||||||
|
services:
|
||||||
|
# ============================================
|
||||||
|
# Traefik Reverse Proxy (SSL/HTTPS)
|
||||||
|
# ============================================
|
||||||
|
traefik:
|
||||||
|
image: traefik:v3.0
|
||||||
|
container_name: monacousa-traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- "--api.dashboard=true"
|
||||||
|
- "--providers.docker=true"
|
||||||
|
- "--providers.docker.exposedbydefault=false"
|
||||||
|
- "--entrypoints.web.address=:80"
|
||||||
|
- "--entrypoints.websecure.address=:443"
|
||||||
|
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
|
||||||
|
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
|
||||||
|
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||||
|
- "--log.level=INFO"
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- traefik-certs:/letsencrypt
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
labels:
|
||||||
|
# Traefik dashboard (optional - remove in production if not needed)
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.routers.traefik.service=api@internal"
|
||||||
|
- "traefik.http.routers.traefik.middlewares=traefik-auth"
|
||||||
|
- "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_AUTH:-}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# PostgreSQL Database
|
||||||
|
# ============================================
|
||||||
|
db:
|
||||||
|
image: supabase/postgres:15.8.1.060
|
||||||
|
container_name: monacousa-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
JWT_EXP: ${JWT_EXPIRY:-3600}
|
||||||
|
volumes:
|
||||||
|
- db-data:/var/lib/postgresql/data
|
||||||
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
reservations:
|
||||||
|
memory: 512M
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Supabase Studio (Dashboard) - Optional
|
||||||
|
# ============================================
|
||||||
|
studio:
|
||||||
|
image: supabase/studio:20241202-71e5240
|
||||||
|
container_name: monacousa-studio
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
STUDIO_PG_META_URL: http://meta:8080
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
DEFAULT_ORGANIZATION_NAME: Monaco USA
|
||||||
|
DEFAULT_PROJECT_NAME: Monaco USA Portal
|
||||||
|
SUPABASE_URL: http://kong:8000
|
||||||
|
SUPABASE_PUBLIC_URL: https://api.${DOMAIN}
|
||||||
|
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||||
|
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||||
|
depends_on:
|
||||||
|
meta:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.studio.rule=Host(`studio.${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.studio.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.studio.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.studio.loadbalancer.server.port=3000"
|
||||||
|
- "traefik.http.routers.studio.middlewares=studio-auth"
|
||||||
|
- "traefik.http.middlewares.studio-auth.basicauth.users=${STUDIO_AUTH:-}"
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Kong API Gateway
|
||||||
|
# ============================================
|
||||||
|
kong:
|
||||||
|
image: kong:2.8.1
|
||||||
|
container_name: monacousa-kong
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
KONG_DATABASE: "off"
|
||||||
|
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
|
||||||
|
KONG_DNS_ORDER: LAST,A,CNAME
|
||||||
|
KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
|
||||||
|
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
|
||||||
|
KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
|
||||||
|
volumes:
|
||||||
|
- ./kong.yml:/var/lib/kong/kong.yml:ro
|
||||||
|
depends_on:
|
||||||
|
auth:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.kong.rule=Host(`api.${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.kong.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.kong.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.kong.loadbalancer.server.port=8000"
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# GoTrue (Auth)
|
||||||
|
# ============================================
|
||||||
|
auth:
|
||||||
|
image: supabase/gotrue:v2.164.0
|
||||||
|
container_name: monacousa-auth
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
GOTRUE_API_HOST: 0.0.0.0
|
||||||
|
GOTRUE_API_PORT: 9999
|
||||||
|
API_EXTERNAL_URL: https://api.${DOMAIN}
|
||||||
|
|
||||||
|
GOTRUE_DB_DRIVER: postgres
|
||||||
|
GOTRUE_DB_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?search_path=auth
|
||||||
|
|
||||||
|
GOTRUE_SITE_URL: https://${DOMAIN}
|
||||||
|
GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS:-}
|
||||||
|
GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP:-false}
|
||||||
|
|
||||||
|
GOTRUE_JWT_ADMIN_ROLES: service_role
|
||||||
|
GOTRUE_JWT_AUD: authenticated
|
||||||
|
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
|
||||||
|
GOTRUE_JWT_EXP: ${JWT_EXPIRY:-3600}
|
||||||
|
GOTRUE_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
|
||||||
|
GOTRUE_EXTERNAL_EMAIL_ENABLED: true
|
||||||
|
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: false
|
||||||
|
GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM:-false}
|
||||||
|
|
||||||
|
GOTRUE_SMTP_HOST: ${SMTP_HOST:-}
|
||||||
|
GOTRUE_SMTP_PORT: ${SMTP_PORT:-587}
|
||||||
|
GOTRUE_SMTP_USER: ${SMTP_USER:-}
|
||||||
|
GOTRUE_SMTP_PASS: ${SMTP_PASS:-}
|
||||||
|
GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL:-}
|
||||||
|
GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME:-Monaco USA}
|
||||||
|
GOTRUE_MAILER_URLPATHS_INVITE: /auth/verify
|
||||||
|
GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/verify
|
||||||
|
GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/verify
|
||||||
|
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/verify
|
||||||
|
|
||||||
|
GOTRUE_RATE_LIMIT_EMAIL_SENT: ${RATE_LIMIT_EMAIL_SENT:-100}
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# PostgREST (REST API)
|
||||||
|
# ============================================
|
||||||
|
rest:
|
||||||
|
image: postgrest/postgrest:v12.2.0
|
||||||
|
container_name: monacousa-rest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PGRST_DB_URI: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||||
|
PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS:-public,storage,graphql_public}
|
||||||
|
PGRST_DB_ANON_ROLE: anon
|
||||||
|
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
PGRST_DB_USE_LEGACY_GUCS: "false"
|
||||||
|
PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY:-3600}
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "exit 0"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Realtime
|
||||||
|
# ============================================
|
||||||
|
realtime:
|
||||||
|
image: supabase/realtime:v2.33.58
|
||||||
|
container_name: monacousa-realtime
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 4000
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_USER: supabase_admin
|
||||||
|
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
DB_NAME: ${POSTGRES_DB}
|
||||||
|
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
|
||||||
|
DB_ENC_KEY: supabaserealtime
|
||||||
|
API_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
|
||||||
|
ERL_AFLAGS: -proto_dist inet_tcp
|
||||||
|
DNS_NODES: "''"
|
||||||
|
RLIMIT_NOFILE: "10000"
|
||||||
|
APP_NAME: realtime
|
||||||
|
SEED_SELF_HOST: true
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Storage API
|
||||||
|
# ============================================
|
||||||
|
storage:
|
||||||
|
image: supabase/storage-api:v1.11.13
|
||||||
|
container_name: monacousa-storage
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ANON_KEY: ${ANON_KEY}
|
||||||
|
SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||||
|
POSTGREST_URL: http://rest:3000
|
||||||
|
PGRST_JWT_SECRET: ${JWT_SECRET}
|
||||||
|
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||||
|
FILE_SIZE_LIMIT: 52428800
|
||||||
|
STORAGE_BACKEND: file
|
||||||
|
FILE_STORAGE_BACKEND_PATH: /var/lib/storage
|
||||||
|
TENANT_ID: stub
|
||||||
|
REGION: stub
|
||||||
|
GLOBAL_S3_BUCKET: stub
|
||||||
|
ENABLE_IMAGE_TRANSFORMATION: "true"
|
||||||
|
IMGPROXY_URL: http://imgproxy:8080
|
||||||
|
volumes:
|
||||||
|
- storage-data:/var/lib/storage
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
rest:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Image Proxy (for storage transformations)
|
||||||
|
# ============================================
|
||||||
|
imgproxy:
|
||||||
|
image: darthsim/imgproxy:v3.8.0
|
||||||
|
container_name: monacousa-imgproxy
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
IMGPROXY_BIND: ":8080"
|
||||||
|
IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
|
||||||
|
IMGPROXY_USE_ETAG: "true"
|
||||||
|
IMGPROXY_ENABLE_WEBP_DETECTION: "true"
|
||||||
|
volumes:
|
||||||
|
- storage-data:/var/lib/storage
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "imgproxy", "health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Postgres Meta (for Studio)
|
||||||
|
# ============================================
|
||||||
|
meta:
|
||||||
|
image: supabase/postgres-meta:v0.84.2
|
||||||
|
container_name: monacousa-meta
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PG_META_PORT: 8080
|
||||||
|
PG_META_DB_HOST: db
|
||||||
|
PG_META_DB_PORT: 5432
|
||||||
|
PG_META_DB_NAME: ${POSTGRES_DB}
|
||||||
|
PG_META_DB_USER: supabase_admin
|
||||||
|
PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "exit 0"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Monaco USA Portal (SvelteKit App)
|
||||||
|
# ============================================
|
||||||
|
portal:
|
||||||
|
image: ${PORTAL_IMAGE:-code.letsbe.solutions/letsbe/monacousa-portal:latest}
|
||||||
|
container_name: monacousa-portal
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PUBLIC_SUPABASE_URL: https://api.${DOMAIN}
|
||||||
|
PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
|
||||||
|
SUPABASE_INTERNAL_URL: http://kong:8000
|
||||||
|
NODE_ENV: production
|
||||||
|
ORIGIN: https://${DOMAIN}
|
||||||
|
BODY_SIZE_LIMIT: ${BODY_SIZE_LIMIT:-52428800}
|
||||||
|
depends_on:
|
||||||
|
kong:
|
||||||
|
condition: service_started
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- monacousa-network
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.portal.rule=Host(`${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.portal.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.portal.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.portal.loadbalancer.server.port=3000"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
reservations:
|
||||||
|
memory: 256M
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Networks
|
||||||
|
# ============================================
|
||||||
|
networks:
|
||||||
|
monacousa-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Volumes
|
||||||
|
# ============================================
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
|
driver: local
|
||||||
|
storage-data:
|
||||||
|
driver: local
|
||||||
|
traefik-certs:
|
||||||
|
driver: local
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,196 @@
|
||||||
|
_format_version: "2.1"
|
||||||
|
_transform: true
|
||||||
|
|
||||||
|
###
|
||||||
|
### Consumers / Users
|
||||||
|
###
|
||||||
|
consumers:
|
||||||
|
- username: ANON
|
||||||
|
keyauth_credentials:
|
||||||
|
- key: __ANON_KEY__
|
||||||
|
- username: SERVICE_ROLE
|
||||||
|
keyauth_credentials:
|
||||||
|
- key: __SERVICE_ROLE_KEY__
|
||||||
|
|
||||||
|
###
|
||||||
|
### Access Control Lists
|
||||||
|
###
|
||||||
|
acls:
|
||||||
|
- consumer: ANON
|
||||||
|
group: anon
|
||||||
|
- consumer: SERVICE_ROLE
|
||||||
|
group: admin
|
||||||
|
|
||||||
|
###
|
||||||
|
### API Routes
|
||||||
|
###
|
||||||
|
services:
|
||||||
|
## Redirect /auth/verify to SvelteKit app for email links
|
||||||
|
- name: auth-verify-redirect
|
||||||
|
url: http://portal:3000/auth/verify
|
||||||
|
routes:
|
||||||
|
- name: auth-verify-redirect
|
||||||
|
strip_path: false
|
||||||
|
paths:
|
||||||
|
- /auth/verify
|
||||||
|
preserve_host: false
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
## Auth Service (GoTrue)
|
||||||
|
- name: auth-v1-open
|
||||||
|
url: http://auth:9999/verify
|
||||||
|
routes:
|
||||||
|
- name: auth-v1-open
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /auth/v1/verify
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
- name: auth-v1-open-callback
|
||||||
|
url: http://auth:9999/callback
|
||||||
|
routes:
|
||||||
|
- name: auth-v1-open-callback
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /auth/v1/callback
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
- name: auth-v1-open-authorize
|
||||||
|
url: http://auth:9999/authorize
|
||||||
|
routes:
|
||||||
|
- name: auth-v1-open-authorize
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /auth/v1/authorize
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
- name: auth-v1
|
||||||
|
url: http://auth:9999/
|
||||||
|
routes:
|
||||||
|
- name: auth-v1
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /auth/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
- name: key-auth
|
||||||
|
config:
|
||||||
|
hide_credentials: false
|
||||||
|
- name: acl
|
||||||
|
config:
|
||||||
|
hide_groups_header: true
|
||||||
|
allow:
|
||||||
|
- admin
|
||||||
|
- anon
|
||||||
|
|
||||||
|
## REST Service (PostgREST)
|
||||||
|
- name: rest-v1
|
||||||
|
url: http://rest:3000/
|
||||||
|
routes:
|
||||||
|
- name: rest-v1
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /rest/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
- name: key-auth
|
||||||
|
config:
|
||||||
|
hide_credentials: false
|
||||||
|
- name: acl
|
||||||
|
config:
|
||||||
|
hide_groups_header: true
|
||||||
|
allow:
|
||||||
|
- admin
|
||||||
|
- anon
|
||||||
|
|
||||||
|
## Realtime Service
|
||||||
|
- name: realtime-v1-ws
|
||||||
|
url: http://realtime:4000/socket
|
||||||
|
routes:
|
||||||
|
- name: realtime-v1-ws
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /realtime/v1/websocket
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
- name: key-auth
|
||||||
|
config:
|
||||||
|
hide_credentials: false
|
||||||
|
- name: acl
|
||||||
|
config:
|
||||||
|
hide_groups_header: true
|
||||||
|
allow:
|
||||||
|
- admin
|
||||||
|
- anon
|
||||||
|
|
||||||
|
- name: realtime-v1
|
||||||
|
url: http://realtime:4000/
|
||||||
|
routes:
|
||||||
|
- name: realtime-v1
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /realtime/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
- name: key-auth
|
||||||
|
config:
|
||||||
|
hide_credentials: false
|
||||||
|
- name: acl
|
||||||
|
config:
|
||||||
|
hide_groups_header: true
|
||||||
|
allow:
|
||||||
|
- admin
|
||||||
|
- anon
|
||||||
|
|
||||||
|
## Storage Service - Public objects (no auth required)
|
||||||
|
- name: storage-v1-public
|
||||||
|
url: http://storage:5000/object/public
|
||||||
|
routes:
|
||||||
|
- name: storage-v1-public
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /storage/v1/object/public
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
|
||||||
|
## Storage Service - All other operations (auth required)
|
||||||
|
- name: storage-v1
|
||||||
|
url: http://storage:5000/
|
||||||
|
routes:
|
||||||
|
- name: storage-v1
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /storage/v1/
|
||||||
|
plugins:
|
||||||
|
- name: cors
|
||||||
|
- name: key-auth
|
||||||
|
config:
|
||||||
|
hide_credentials: false
|
||||||
|
- name: acl
|
||||||
|
config:
|
||||||
|
hide_groups_header: true
|
||||||
|
allow:
|
||||||
|
- admin
|
||||||
|
- anon
|
||||||
|
|
||||||
|
## PostgreSQL Meta (for Studio)
|
||||||
|
- name: meta
|
||||||
|
url: http://meta:8080/
|
||||||
|
routes:
|
||||||
|
- name: meta
|
||||||
|
strip_path: true
|
||||||
|
paths:
|
||||||
|
- /pg/
|
||||||
|
plugins:
|
||||||
|
- name: key-auth
|
||||||
|
config:
|
||||||
|
hide_credentials: false
|
||||||
|
- name: acl
|
||||||
|
config:
|
||||||
|
hide_groups_header: true
|
||||||
|
allow:
|
||||||
|
- admin
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Monaco USA Portal - Production Setup Script
|
||||||
|
# This script prepares the deployment environment by:
|
||||||
|
# 1. Generating missing secrets in .env
|
||||||
|
# 2. Generating kong.yml from template with actual API keys
|
||||||
|
# 3. Validating the configuration
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "Monaco USA Portal - Production Setup"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to generate random string
|
||||||
|
generate_secret() {
|
||||||
|
local length=${1:-32}
|
||||||
|
openssl rand -base64 $length | tr -d '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to generate JWT token
|
||||||
|
generate_jwt() {
|
||||||
|
local role=$1
|
||||||
|
local secret=$2
|
||||||
|
|
||||||
|
# JWT Header (base64url encoded)
|
||||||
|
local header='{"alg":"HS256","typ":"JWT"}'
|
||||||
|
local header_b64=$(echo -n "$header" | base64 | tr '+/' '-_' | tr -d '=')
|
||||||
|
|
||||||
|
# JWT Payload - 100 years expiry
|
||||||
|
local exp=$(($(date +%s) + 3153600000))
|
||||||
|
local payload="{\"role\":\"$role\",\"iss\":\"supabase\",\"iat\":$(date +%s),\"exp\":$exp}"
|
||||||
|
local payload_b64=$(echo -n "$payload" | base64 | tr '+/' '-_' | tr -d '=')
|
||||||
|
|
||||||
|
# Create signature
|
||||||
|
local signature=$(echo -n "${header_b64}.${payload_b64}" | openssl dgst -sha256 -hmac "$secret" -binary | base64 | tr '+/' '-_' | tr -d '=')
|
||||||
|
|
||||||
|
echo "${header_b64}.${payload_b64}.${signature}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if .env exists
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
if [ -f .env.example ]; then
|
||||||
|
echo -e "${YELLOW}No .env file found. Creating from .env.example...${NC}"
|
||||||
|
cp .env.example .env
|
||||||
|
echo -e "${GREEN}Created .env from template.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Error: No .env or .env.example file found.${NC}"
|
||||||
|
echo "Please create a .env file with your configuration."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load environment
|
||||||
|
set -a
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Checking and generating secrets..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Track if we made changes
|
||||||
|
CHANGES_MADE=false
|
||||||
|
|
||||||
|
# Generate POSTGRES_PASSWORD if not set or is placeholder
|
||||||
|
if [ -z "$POSTGRES_PASSWORD" ] || [[ "$POSTGRES_PASSWORD" == *"CHANGE_ME"* ]] || [[ "$POSTGRES_PASSWORD" == *"change-this"* ]]; then
|
||||||
|
NEW_POSTGRES_PASSWORD=$(generate_secret 32)
|
||||||
|
sed -i.bak "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$NEW_POSTGRES_PASSWORD|" .env
|
||||||
|
POSTGRES_PASSWORD=$NEW_POSTGRES_PASSWORD
|
||||||
|
echo -e "${GREEN}[Generated]${NC} POSTGRES_PASSWORD"
|
||||||
|
CHANGES_MADE=true
|
||||||
|
else
|
||||||
|
echo -e "[OK] POSTGRES_PASSWORD is set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate JWT_SECRET if not set or is placeholder
|
||||||
|
if [ -z "$JWT_SECRET" ] || [[ "$JWT_SECRET" == *"CHANGE_ME"* ]] || [[ "$JWT_SECRET" == *"generate"* ]]; then
|
||||||
|
NEW_JWT_SECRET=$(generate_secret 32)
|
||||||
|
sed -i.bak "s|^JWT_SECRET=.*|JWT_SECRET=$NEW_JWT_SECRET|" .env
|
||||||
|
JWT_SECRET=$NEW_JWT_SECRET
|
||||||
|
echo -e "${GREEN}[Generated]${NC} JWT_SECRET"
|
||||||
|
CHANGES_MADE=true
|
||||||
|
else
|
||||||
|
echo -e "[OK] JWT_SECRET is set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate SECRET_KEY_BASE if not set or is placeholder
|
||||||
|
if [ -z "$SECRET_KEY_BASE" ] || [[ "$SECRET_KEY_BASE" == *"CHANGE_ME"* ]] || [[ "$SECRET_KEY_BASE" == *"generate"* ]]; then
|
||||||
|
NEW_SECRET_KEY_BASE=$(generate_secret 64)
|
||||||
|
sed -i.bak "s|^SECRET_KEY_BASE=.*|SECRET_KEY_BASE=$NEW_SECRET_KEY_BASE|" .env
|
||||||
|
SECRET_KEY_BASE=$NEW_SECRET_KEY_BASE
|
||||||
|
echo -e "${GREEN}[Generated]${NC} SECRET_KEY_BASE"
|
||||||
|
CHANGES_MADE=true
|
||||||
|
else
|
||||||
|
echo -e "[OK] SECRET_KEY_BASE is set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate ANON_KEY if not set or is placeholder
|
||||||
|
if [ -z "$ANON_KEY" ] || [[ "$ANON_KEY" == *"CHANGE_ME"* ]] || [[ "$ANON_KEY" == *"your-"* ]]; then
|
||||||
|
NEW_ANON_KEY=$(generate_jwt "anon" "$JWT_SECRET")
|
||||||
|
sed -i.bak "s|^ANON_KEY=.*|ANON_KEY=$NEW_ANON_KEY|" .env
|
||||||
|
ANON_KEY=$NEW_ANON_KEY
|
||||||
|
echo -e "${GREEN}[Generated]${NC} ANON_KEY (JWT with role=anon)"
|
||||||
|
CHANGES_MADE=true
|
||||||
|
else
|
||||||
|
echo -e "[OK] ANON_KEY is set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate SERVICE_ROLE_KEY if not set or is placeholder
|
||||||
|
if [ -z "$SERVICE_ROLE_KEY" ] || [[ "$SERVICE_ROLE_KEY" == *"CHANGE_ME"* ]] || [[ "$SERVICE_ROLE_KEY" == *"your-"* ]]; then
|
||||||
|
NEW_SERVICE_ROLE_KEY=$(generate_jwt "service_role" "$JWT_SECRET")
|
||||||
|
sed -i.bak "s|^SERVICE_ROLE_KEY=.*|SERVICE_ROLE_KEY=$NEW_SERVICE_ROLE_KEY|" .env
|
||||||
|
SERVICE_ROLE_KEY=$NEW_SERVICE_ROLE_KEY
|
||||||
|
echo -e "${GREEN}[Generated]${NC} SERVICE_ROLE_KEY (JWT with role=service_role)"
|
||||||
|
CHANGES_MADE=true
|
||||||
|
else
|
||||||
|
echo -e "[OK] SERVICE_ROLE_KEY is set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Also update PUBLIC_SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY if they exist
|
||||||
|
if grep -q "^PUBLIC_SUPABASE_ANON_KEY=" .env; then
|
||||||
|
sed -i.bak "s|^PUBLIC_SUPABASE_ANON_KEY=.*|PUBLIC_SUPABASE_ANON_KEY=$ANON_KEY|" .env
|
||||||
|
fi
|
||||||
|
if grep -q "^SUPABASE_SERVICE_ROLE_KEY=" .env; then
|
||||||
|
sed -i.bak "s|^SUPABASE_SERVICE_ROLE_KEY=.*|SUPABASE_SERVICE_ROLE_KEY=$SERVICE_ROLE_KEY|" .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up backup files
|
||||||
|
rm -f .env.bak
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Reload environment after changes
|
||||||
|
set -a
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
# Validate required variables
|
||||||
|
echo "Validating required variables..."
|
||||||
|
REQUIRED_VARS=(
|
||||||
|
"DOMAIN"
|
||||||
|
"POSTGRES_USER"
|
||||||
|
"POSTGRES_PASSWORD"
|
||||||
|
"POSTGRES_DB"
|
||||||
|
"JWT_SECRET"
|
||||||
|
"ANON_KEY"
|
||||||
|
"SERVICE_ROLE_KEY"
|
||||||
|
"SECRET_KEY_BASE"
|
||||||
|
)
|
||||||
|
|
||||||
|
MISSING_VARS=()
|
||||||
|
for var in "${REQUIRED_VARS[@]}"; do
|
||||||
|
if [ -z "${!var}" ]; then
|
||||||
|
MISSING_VARS+=("$var")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#MISSING_VARS[@]} -ne 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${RED}Error: The following required variables are not set in .env:${NC}"
|
||||||
|
for var in "${MISSING_VARS[@]}"; do
|
||||||
|
echo " - $var"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "Please edit .env and set these values, then run this script again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}All required variables are set.${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check for optional but recommended variables
|
||||||
|
OPTIONAL_VARS=(
|
||||||
|
"ACME_EMAIL"
|
||||||
|
"SMTP_HOST"
|
||||||
|
"SMTP_USER"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Checking optional variables..."
|
||||||
|
for var in "${OPTIONAL_VARS[@]}"; do
|
||||||
|
if [ -z "${!var}" ]; then
|
||||||
|
echo -e "${YELLOW}[Warning]${NC} $var is not set (optional)"
|
||||||
|
else
|
||||||
|
echo -e "[OK] $var is set"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Generate kong.yml from template
|
||||||
|
echo "Generating kong.yml from template..."
|
||||||
|
|
||||||
|
if [ ! -f kong.yml.template ]; then
|
||||||
|
echo -e "${RED}Error: kong.yml.template not found.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use sed to replace placeholders
|
||||||
|
sed -e "s|__ANON_KEY__|$ANON_KEY|g" \
|
||||||
|
-e "s|__SERVICE_ROLE_KEY__|$SERVICE_ROLE_KEY|g" \
|
||||||
|
kong.yml.template > kong.yml
|
||||||
|
|
||||||
|
echo -e "${GREEN}Generated kong.yml with API keys.${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
echo "========================================"
|
||||||
|
echo "Setup Complete!"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
if [ "$CHANGES_MADE" = true ]; then
|
||||||
|
echo -e "${YELLOW}IMPORTANT: New secrets were generated and saved to .env${NC}"
|
||||||
|
echo "Make sure to backup your .env file securely!"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Review .env and configure any remaining settings"
|
||||||
|
echo " 2. Start the services: docker compose up -d"
|
||||||
|
echo " 3. Wait for all containers to be healthy: docker compose ps"
|
||||||
|
echo " 4. Access the portal at: https://${DOMAIN:-your-domain.com}"
|
||||||
|
echo " 5. Create your admin account on first visit"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " docker compose ps - Check container status"
|
||||||
|
echo " docker compose logs -f - Follow all logs"
|
||||||
|
echo " docker compose logs db - Check database logs"
|
||||||
|
echo " docker compose down - Stop all services"
|
||||||
|
echo ""
|
||||||
Loading…
Reference in New Issue