From 22a58eecef11161ca0465bdbfe93b8e6aacfbae6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Jan 2026 20:42:59 +0000 Subject: [PATCH] feat(deploy): add production deployment configuration for manacore.ai Complete production setup with: - docker-compose.yml with all recommended apps (auth, chat, todo, calendar, clock, dashboard) - Caddyfile for automatic HTTPS via Let's Encrypt - PostgreSQL backup/restore scripts with daily retention - Environment template with secure defaults - Comprehensive deployment guide Apps deployed: - auth.manacore.ai (Authentication) - app.manacore.ai (Dashboard) - chat.manacore.ai / chat-api.manacore.ai - todo.manacore.ai / todo-api.manacore.ai - calendar.manacore.ai / calendar-api.manacore.ai - clock.manacore.ai / clock-api.manacore.ai --- docker/production/.env.template | 78 ++++ docker/production/.gitignore | 8 + docker/production/Caddyfile | 169 +++++++++ docker/production/DEPLOY.md | 346 +++++++++++++++++ docker/production/backup.sh | 99 +++++ docker/production/docker-compose.yml | 531 +++++++++++++++++++++++++++ docker/production/restore.sh | 82 +++++ 7 files changed, 1313 insertions(+) create mode 100644 docker/production/.env.template create mode 100644 docker/production/.gitignore create mode 100644 docker/production/Caddyfile create mode 100644 docker/production/DEPLOY.md create mode 100755 docker/production/backup.sh create mode 100644 docker/production/docker-compose.yml create mode 100755 docker/production/restore.sh diff --git a/docker/production/.env.template b/docker/production/.env.template new file mode 100644 index 000000000..089298534 --- /dev/null +++ b/docker/production/.env.template @@ -0,0 +1,78 @@ +# ManaCore Production Environment Variables +# Copy this file to .env and fill in the values: +# cp .env.template .env +# nano .env +# +# IMPORTANT: Never commit the .env file to git! + +# ============================================ +# Docker Registry +# ============================================ +DOCKER_REGISTRY=ghcr.io/memo-2023 + +# ============================================ +# PostgreSQL +# ============================================ +POSTGRES_DB=manacore +POSTGRES_USER=postgres +# Generate with: openssl rand -base64 32 +POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD + +# ============================================ +# Redis +# ============================================ +# Generate with: openssl rand -base64 32 +REDIS_PASSWORD=CHANGE_ME_STRONG_PASSWORD + +# ============================================ +# JWT Configuration +# ============================================ +# Generate secret with: openssl rand -base64 64 +JWT_SECRET=CHANGE_ME_STRONG_SECRET + +# EdDSA Key Pair (Ed25519) +# Generate with: node -e "const { generateKeyPairSync } = require('crypto'); const { publicKey, privateKey } = generateKeyPairSync('ed25519'); console.log('PUBLIC:', publicKey.export({type:'spki',format:'pem'}).toString().replace(/\\n/g,'\\\\n')); console.log('PRIVATE:', privateKey.export({type:'pkcs8',format:'pem'}).toString().replace(/\\n/g,'\\\\n'));" +JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nYOUR_PUBLIC_KEY_HERE\n-----END PUBLIC KEY-----" +JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----" + +JWT_ACCESS_TOKEN_EXPIRY=15m +JWT_REFRESH_TOKEN_EXPIRY=7d + +# ============================================ +# Service Versions (optional, defaults to latest) +# ============================================ +AUTH_VERSION=latest +CHAT_VERSION=latest +CHAT_WEB_VERSION=latest +TODO_VERSION=latest +TODO_WEB_VERSION=latest +CALENDAR_VERSION=latest +CALENDAR_WEB_VERSION=latest +CLOCK_VERSION=latest +CLOCK_WEB_VERSION=latest +MANACORE_WEB_VERSION=latest + +# ============================================ +# Azure OpenAI (for Chat) +# ============================================ +AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com +AZURE_OPENAI_API_KEY=your-api-key +AZURE_OPENAI_API_VERSION=2024-12-01-preview + +# ============================================ +# Credits System +# ============================================ +CREDITS_SIGNUP_BONUS=100 +CREDITS_DAILY_FREE=5 + +# ============================================ +# OAuth (optional) +# ============================================ +# GOOGLE_CLIENT_ID= +# GOOGLE_CLIENT_SECRET= + +# ============================================ +# Stripe (optional) +# ============================================ +# STRIPE_SECRET_KEY=sk_live_... +# STRIPE_WEBHOOK_SECRET=whsec_... diff --git a/docker/production/.gitignore b/docker/production/.gitignore new file mode 100644 index 000000000..7a1acfd32 --- /dev/null +++ b/docker/production/.gitignore @@ -0,0 +1,8 @@ +# Never commit production secrets +.env +*.env.local + +# Backup files (stored on server, not in git) +backups/ +*.sql +*.sql.gz diff --git a/docker/production/Caddyfile b/docker/production/Caddyfile new file mode 100644 index 000000000..19a77a48f --- /dev/null +++ b/docker/production/Caddyfile @@ -0,0 +1,169 @@ +# ManaCore Production Reverse Proxy +# Domain: manacore.ai +# +# Features: +# - Automatic HTTPS via Let's Encrypt +# - HTTP/2 and HTTP/3 support +# - Gzip compression +# - Security headers +# - Health check endpoints +# +# Reload: docker exec manacore-caddy caddy reload --config /etc/caddy/Caddyfile + +# Global options +{ + email admin@manacore.ai + # Uncomment for staging/testing Let's Encrypt (higher rate limits) + # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory +} + +# Common security headers snippet +(security_headers) { + header { + # Security headers + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + X-XSS-Protection "1; mode=block" + Referrer-Policy "strict-origin-when-cross-origin" + # Remove server identification + -Server + } +} + +# Common compression snippet +(compression) { + encode gzip zstd +} + +# ============================================ +# Auth Service +# ============================================ + +auth.manacore.ai { + import security_headers + import compression + + reverse_proxy mana-core-auth:3001 { + health_uri /api/v1/health + health_interval 30s + } +} + +# ============================================ +# Main Dashboard +# ============================================ + +app.manacore.ai { + import security_headers + import compression + + reverse_proxy manacore-web:5173 { + health_uri /health + health_interval 30s + } +} + +# Redirect root domain to app +manacore.ai { + redir https://app.manacore.ai{uri} permanent +} + +www.manacore.ai { + redir https://app.manacore.ai{uri} permanent +} + +# ============================================ +# Chat App +# ============================================ + +chat.manacore.ai { + import security_headers + import compression + + reverse_proxy chat-web:3000 { + health_uri /health + health_interval 30s + } +} + +chat-api.manacore.ai { + import security_headers + import compression + + reverse_proxy chat-backend:3002 { + health_uri /api/v1/health + health_interval 30s + } +} + +# ============================================ +# Todo App +# ============================================ + +todo.manacore.ai { + import security_headers + import compression + + reverse_proxy todo-web:5188 { + health_uri /health + health_interval 30s + } +} + +todo-api.manacore.ai { + import security_headers + import compression + + reverse_proxy todo-backend:3018 { + health_uri /api/v1/health + health_interval 30s + } +} + +# ============================================ +# Calendar App +# ============================================ + +calendar.manacore.ai { + import security_headers + import compression + + reverse_proxy calendar-web:5186 { + health_uri /health + health_interval 30s + } +} + +calendar-api.manacore.ai { + import security_headers + import compression + + reverse_proxy calendar-backend:3016 { + health_uri /api/v1/health + health_interval 30s + } +} + +# ============================================ +# Clock App +# ============================================ + +clock.manacore.ai { + import security_headers + import compression + + reverse_proxy clock-web:5187 { + health_uri /health + health_interval 30s + } +} + +clock-api.manacore.ai { + import security_headers + import compression + + reverse_proxy clock-backend:3017 { + health_uri /api/v1/health + health_interval 30s + } +} diff --git a/docker/production/DEPLOY.md b/docker/production/DEPLOY.md new file mode 100644 index 000000000..4bc2f17ce --- /dev/null +++ b/docker/production/DEPLOY.md @@ -0,0 +1,346 @@ +# ManaCore Production Deployment Guide + +One-Server-Setup for manacore.ai with automated HTTPS and daily backups. + +## Quick Overview + +| Domain | Service | Port | +|--------|---------|------| +| auth.manacore.ai | Auth Service | 3001 | +| app.manacore.ai | Dashboard | 5173 | +| chat.manacore.ai | Chat Web | 3000 | +| chat-api.manacore.ai | Chat Backend | 3002 | +| todo.manacore.ai | Todo Web | 5188 | +| todo-api.manacore.ai | Todo Backend | 3018 | +| calendar.manacore.ai | Calendar Web | 5186 | +| calendar-api.manacore.ai | Calendar Backend | 3016 | +| clock.manacore.ai | Clock Web | 5187 | +| clock-api.manacore.ai | Clock Backend | 3017 | + +## Prerequisites + +### 1. Server Requirements (Hetzner CAX21 recommended) + +- 4 vCPU, 8GB RAM, 80GB SSD (~8€/month) +- Ubuntu 22.04 or Debian 12 +- Docker + Docker Compose v2 + +### 2. DNS Configuration + +Add these A records pointing to your server IP: + +``` +manacore.ai → YOUR_SERVER_IP +www.manacore.ai → YOUR_SERVER_IP +app.manacore.ai → YOUR_SERVER_IP +auth.manacore.ai → YOUR_SERVER_IP +chat.manacore.ai → YOUR_SERVER_IP +chat-api.manacore.ai → YOUR_SERVER_IP +todo.manacore.ai → YOUR_SERVER_IP +todo-api.manacore.ai → YOUR_SERVER_IP +calendar.manacore.ai → YOUR_SERVER_IP +calendar-api.manacore.ai → YOUR_SERVER_IP +clock.manacore.ai → YOUR_SERVER_IP +clock-api.manacore.ai → YOUR_SERVER_IP +``` + +--- + +## Server Setup (First Time) + +### Step 1: Install Docker + +```bash +# SSH into server +ssh root@YOUR_SERVER_IP + +# Install Docker +curl -fsSL https://get.docker.com | sh + +# Add user to docker group (optional) +usermod -aG docker $USER + +# Verify installation +docker --version +docker compose version +``` + +### Step 2: Create Project Directory + +```bash +mkdir -p /opt/manacore +cd /opt/manacore +``` + +### Step 3: Copy Files to Server + +From your local machine: + +```bash +# Copy production files +scp docker/production/docker-compose.yml root@YOUR_SERVER_IP:/opt/manacore/ +scp docker/production/Caddyfile root@YOUR_SERVER_IP:/opt/manacore/ +scp docker/production/.env.template root@YOUR_SERVER_IP:/opt/manacore/ +scp docker/production/backup.sh root@YOUR_SERVER_IP:/opt/manacore/ +scp docker/production/restore.sh root@YOUR_SERVER_IP:/opt/manacore/ +``` + +### Step 4: Configure Environment + +```bash +cd /opt/manacore + +# Copy template +cp .env.template .env + +# Generate secure passwords +echo "POSTGRES_PASSWORD=$(openssl rand -base64 32)" >> .env.generated +echo "REDIS_PASSWORD=$(openssl rand -base64 32)" >> .env.generated +echo "JWT_SECRET=$(openssl rand -base64 64)" >> .env.generated + +# Edit .env with your values +nano .env +``` + +### Step 5: Generate JWT Keys + +```bash +# Generate Ed25519 key pair +node -e " +const { generateKeyPairSync } = require('crypto'); +const { publicKey, privateKey } = generateKeyPairSync('ed25519'); +console.log('JWT_PUBLIC_KEY=\"' + publicKey.export({type:'spki',format:'pem'}).toString().replace(/\n/g,'\\\\n') + '\"'); +console.log('JWT_PRIVATE_KEY=\"' + privateKey.export({type:'pkcs8',format:'pem'}).toString().replace(/\n/g,'\\\\n') + '\"'); +" +``` + +Copy the output to your `.env` file. + +### Step 6: Create Backup Directory + +```bash +mkdir -p /opt/manacore/backups +chmod +x backup.sh restore.sh +``` + +### Step 7: Login to Docker Registry + +```bash +# Create GitHub token with read:packages permission +docker login ghcr.io -u YOUR_GITHUB_USERNAME +# Enter your GitHub Personal Access Token +``` + +--- + +## Deployment + +### Initial Deployment + +```bash +cd /opt/manacore + +# Pull all images +docker compose pull + +# Start all services +docker compose up -d + +# Watch logs +docker compose logs -f +``` + +### Verify Services + +```bash +# Check all containers are running +docker compose ps + +# Test health endpoints +curl -s http://localhost:3001/api/v1/health # Auth +curl -s http://localhost:3002/api/v1/health # Chat Backend +curl -s http://localhost:3018/api/v1/health # Todo Backend +curl -s http://localhost:3016/api/v1/health # Calendar Backend +curl -s http://localhost:3017/api/v1/health # Clock Backend +``` + +### Initialize Databases + +The databases are created automatically on first start, but you may need to run migrations: + +```bash +# Create databases if not exist +docker exec manacore-postgres psql -U postgres -c "CREATE DATABASE manacore_auth;" +docker exec manacore-postgres psql -U postgres -c "CREATE DATABASE chat;" +docker exec manacore-postgres psql -U postgres -c "CREATE DATABASE todo;" +docker exec manacore-postgres psql -U postgres -c "CREATE DATABASE calendar;" +docker exec manacore-postgres psql -U postgres -c "CREATE DATABASE clock;" +``` + +--- + +## Daily Operations + +### Update Services + +```bash +cd /opt/manacore + +# Pull latest images +docker compose pull + +# Restart with new images (zero-downtime for stateless services) +docker compose up -d + +# Or restart specific service +docker compose pull chat-backend +docker compose up -d chat-backend +``` + +### View Logs + +```bash +# All services +docker compose logs -f + +# Specific service +docker compose logs -f chat-backend + +# Last 100 lines +docker compose logs --tail=100 mana-core-auth +``` + +### Backup + +```bash +# Manual backup +./backup.sh + +# Backup specific database +./backup.sh chat + +# List backups +ls -lh backups/ +``` + +### Restore + +```bash +# Restore from backup (CAUTION: overwrites data!) +./restore.sh chat backups/chat_20250116_030000.sql.gz +``` + +### Setup Daily Backups (Cron) + +```bash +crontab -e + +# Add this line (backup at 3 AM daily) +0 3 * * * /opt/manacore/backup.sh >> /var/log/manacore-backup.log 2>&1 +``` + +--- + +## Troubleshooting + +### Service won't start + +```bash +# Check logs +docker compose logs SERVICE_NAME + +# Check container status +docker inspect SERVICE_NAME + +# Restart service +docker compose restart SERVICE_NAME +``` + +### Database connection issues + +```bash +# Test database connection +docker exec manacore-postgres psql -U postgres -c "\l" + +# Check if database exists +docker exec manacore-postgres psql -U postgres -c "\c chat" +``` + +### Certificate issues (HTTPS) + +```bash +# Caddy automatically obtains certificates +# Check Caddy logs for errors +docker compose logs caddy + +# Force certificate renewal +docker exec manacore-caddy caddy reload --config /etc/caddy/Caddyfile +``` + +### Out of disk space + +```bash +# Check disk usage +df -h + +# Clean up Docker +docker system prune -a --volumes + +# Clean old backups +find /opt/manacore/backups -name "*.sql.gz" -mtime +7 -delete +``` + +--- + +## CI/CD Integration + +The GitHub Actions workflow can be updated to deploy automatically: + +```yaml +# .github/workflows/cd-production.yml +deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Production + uses: appleboy/ssh-action@v1 + with: + host: ${{ secrets.PROD_SERVER_IP }} + username: root + key: ${{ secrets.PROD_SSH_KEY }} + script: | + cd /opt/manacore + docker compose pull + docker compose up -d +``` + +--- + +## Resource Usage + +Approximate resource usage per service: + +| Service | CPU | Memory | +|---------|-----|--------| +| PostgreSQL | 0.5 | 512MB | +| Redis | 0.1 | 256MB | +| Caddy | 0.1 | 64MB | +| mana-core-auth | 0.5-1 | 256-512MB | +| chat-backend | 1-2 | 512MB-1GB | +| chat-web | 0.2 | 128MB | +| Other backends | 0.2-0.5 | 128-256MB | +| Other webs | 0.2 | 128MB | + +**Total**: ~2-4 vCPU, ~3-4GB RAM + +Recommended server: **Hetzner CAX21** (4 vCPU ARM, 8GB RAM) for ~8€/month + +--- + +## Security Checklist + +- [ ] Strong passwords in `.env` (use `openssl rand -base64 32`) +- [ ] Firewall: Only ports 80, 443, 22 open +- [ ] SSH key authentication (disable password login) +- [ ] Regular backups (daily cron) +- [ ] Keep Docker and OS updated +- [ ] Monitor logs for anomalies diff --git a/docker/production/backup.sh b/docker/production/backup.sh new file mode 100755 index 000000000..0fe3c0060 --- /dev/null +++ b/docker/production/backup.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# ManaCore PostgreSQL Backup Script +# +# Usage: +# ./backup.sh # Backup all databases +# ./backup.sh chat # Backup specific database +# +# Setup cron for daily backups: +# crontab -e +# 0 3 * * * /opt/manacore/backup.sh >> /var/log/manacore-backup.log 2>&1 + +set -euo pipefail + +# Configuration +BACKUP_DIR="${BACKUP_DIR:-./backups}" +CONTAINER_NAME="${CONTAINER_NAME:-manacore-postgres}" +POSTGRES_USER="${POSTGRES_USER:-postgres}" +RETENTION_DAYS="${RETENTION_DAYS:-7}" + +# Databases to backup (add more as needed) +DATABASES=("manacore_auth" "chat" "todo" "calendar" "clock") + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +# Create backup directory if not exists +mkdir -p "$BACKUP_DIR" + +# Timestamp for backup files +TIMESTAMP=$(date '+%Y%m%d_%H%M%S') + +backup_database() { + local db_name=$1 + local backup_file="${BACKUP_DIR}/${db_name}_${TIMESTAMP}.sql.gz" + + log_info "Backing up database: $db_name" + + if docker exec "$CONTAINER_NAME" pg_dump -U "$POSTGRES_USER" "$db_name" 2>/dev/null | gzip > "$backup_file"; then + local size=$(du -h "$backup_file" | cut -f1) + log_info "Created: $backup_file ($size)" + return 0 + else + log_error "Failed to backup $db_name" + rm -f "$backup_file" + return 1 + fi +} + +cleanup_old_backups() { + log_info "Cleaning up backups older than $RETENTION_DAYS days..." + find "$BACKUP_DIR" -name "*.sql.gz" -type f -mtime +$RETENTION_DAYS -delete + local count=$(find "$BACKUP_DIR" -name "*.sql.gz" -type f | wc -l) + log_info "Remaining backups: $count" +} + +# Main execution +log_info "=== ManaCore Backup Started ===" + +# Check if specific database requested +if [ $# -eq 1 ]; then + backup_database "$1" +else + # Backup all databases + failed=0 + for db in "${DATABASES[@]}"; do + if ! backup_database "$db"; then + ((failed++)) + fi + done + + if [ $failed -gt 0 ]; then + log_warn "$failed database(s) failed to backup" + fi +fi + +# Cleanup old backups +cleanup_old_backups + +log_info "=== ManaCore Backup Completed ===" + +# Summary +echo "" +log_info "Backup summary:" +ls -lh "$BACKUP_DIR"/*.sql.gz 2>/dev/null | tail -10 || log_warn "No backups found" diff --git a/docker/production/docker-compose.yml b/docker/production/docker-compose.yml new file mode 100644 index 000000000..f97076c0f --- /dev/null +++ b/docker/production/docker-compose.yml @@ -0,0 +1,531 @@ +# ManaCore Production Stack +# Domain: manacore.ai +# +# Apps included: +# - mana-core-auth (auth.manacore.ai) +# - chat (chat.manacore.ai + chat-api.manacore.ai) +# - todo (todo.manacore.ai + todo-api.manacore.ai) +# - calendar (calendar.manacore.ai + calendar-api.manacore.ai) +# - clock (clock.manacore.ai + clock-api.manacore.ai) +# - manacore-web (app.manacore.ai) +# +# Usage: +# docker compose -f docker-compose.yml up -d +# docker compose -f docker-compose.yml logs -f +# docker compose -f docker-compose.yml down + +services: + # ============================================ + # Infrastructure Services + # ============================================ + + postgres: + image: postgres:16-alpine + container_name: manacore-postgres + restart: always + environment: + POSTGRES_DB: ${POSTGRES_DB:-manacore} + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + volumes: + - postgres_data:/var/lib/postgresql/data + - ./backups:/backups + ports: + - "127.0.0.1:5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "5" + + redis: + image: redis:7-alpine + container_name: manacore-redis + restart: always + command: redis-server --requirepass ${REDIS_PASSWORD:?REDIS_PASSWORD is required} --maxmemory 256mb --maxmemory-policy allkeys-lru + volumes: + - redis_data:/data + ports: + - "127.0.0.1:6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + + # ============================================ + # Reverse Proxy (Caddy - Auto HTTPS) + # ============================================ + + caddy: + image: caddy:2-alpine + container_name: manacore-caddy + restart: always + ports: + - "80:80" + - "443:443" + - "443:443/udp" # HTTP/3 + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + + # ============================================ + # Auth Service + # ============================================ + + mana-core-auth: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/mana-core-auth:${AUTH_VERSION:-latest} + container_name: mana-core-auth + restart: always + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3001 + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/manacore_auth + REDIS_HOST: redis + REDIS_PORT: 6379 + REDIS_PASSWORD: ${REDIS_PASSWORD} + JWT_SECRET: ${JWT_SECRET} + JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY} + JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY} + JWT_ACCESS_TOKEN_EXPIRY: ${JWT_ACCESS_TOKEN_EXPIRY:-15m} + JWT_REFRESH_TOKEN_EXPIRY: ${JWT_REFRESH_TOKEN_EXPIRY:-7d} + CORS_ORIGINS: https://app.manacore.ai,https://chat.manacore.ai,https://todo.manacore.ai,https://calendar.manacore.ai,https://clock.manacore.ai + CREDITS_SIGNUP_BONUS: ${CREDITS_SIGNUP_BONUS:-100} + CREDITS_DAILY_FREE: ${CREDITS_DAILY_FREE:-5} + # OAuth (optional) + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} + # Stripe (optional) + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-} + ports: + - "127.0.0.1:3001:3001" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "5" + deploy: + resources: + limits: + cpus: '1' + memory: 512M + + # ============================================ + # Chat App + # ============================================ + + chat-backend: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/chat-backend:${CHAT_VERSION:-latest} + container_name: chat-backend + restart: always + depends_on: + mana-core-auth: + condition: service_healthy + postgres: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3002 + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/chat + MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + AZURE_OPENAI_ENDPOINT: ${AZURE_OPENAI_ENDPOINT} + AZURE_OPENAI_API_KEY: ${AZURE_OPENAI_API_KEY} + AZURE_OPENAI_API_VERSION: ${AZURE_OPENAI_API_VERSION:-2024-12-01-preview} + CORS_ORIGINS: https://chat.manacore.ai,https://app.manacore.ai + ports: + - "127.0.0.1:3002:3002" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3002/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "5" + deploy: + resources: + limits: + cpus: '2' + memory: 1G + + chat-web: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/chat-web:${CHAT_WEB_VERSION:-latest} + container_name: chat-web + restart: always + depends_on: + chat-backend: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3000 + # Server-side URLs (Docker internal) + PUBLIC_BACKEND_URL: http://chat-backend:3002 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + # Client-side URLs (browser) + PUBLIC_BACKEND_URL_CLIENT: https://chat-api.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.manacore.ai + ports: + - "127.0.0.1:3000:3000" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + # ============================================ + # ManaCore Dashboard + # ============================================ + + manacore-web: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/manacore-web:${MANACORE_WEB_VERSION:-latest} + container_name: manacore-web + restart: always + depends_on: + mana-core-auth: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 5173 + # Auth + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.manacore.ai + # Backend URLs for dashboard widgets + PUBLIC_TODO_API_URL: http://todo-backend:3018 + PUBLIC_TODO_API_URL_CLIENT: https://todo-api.manacore.ai + PUBLIC_CALENDAR_API_URL: http://calendar-backend:3016 + PUBLIC_CALENDAR_API_URL_CLIENT: https://calendar-api.manacore.ai + PUBLIC_CLOCK_API_URL: http://clock-backend:3017 + PUBLIC_CLOCK_API_URL_CLIENT: https://clock-api.manacore.ai + PUBLIC_CHAT_API_URL: http://chat-backend:3002 + PUBLIC_CHAT_API_URL_CLIENT: https://chat-api.manacore.ai + ports: + - "127.0.0.1:5173:5173" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5173/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + # ============================================ + # Todo App + # ============================================ + + todo-backend: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/todo-backend:${TODO_VERSION:-latest} + container_name: todo-backend + restart: always + depends_on: + mana-core-auth: + condition: service_healthy + postgres: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3018 + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/todo + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${POSTGRES_USER:-postgres} + MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + CORS_ORIGINS: https://todo.manacore.ai,https://app.manacore.ai + ports: + - "127.0.0.1:3018:3018" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3018/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + todo-web: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/todo-web:${TODO_WEB_VERSION:-latest} + container_name: todo-web + restart: always + depends_on: + todo-backend: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 5188 + PUBLIC_BACKEND_URL: http://todo-backend:3018 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + PUBLIC_BACKEND_URL_CLIENT: https://todo-api.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.manacore.ai + ports: + - "127.0.0.1:5188:5188" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5188/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + # ============================================ + # Calendar App + # ============================================ + + calendar-backend: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/calendar-backend:${CALENDAR_VERSION:-latest} + container_name: calendar-backend + restart: always + depends_on: + mana-core-auth: + condition: service_healthy + postgres: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3016 + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/calendar + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${POSTGRES_USER:-postgres} + MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + CORS_ORIGINS: https://calendar.manacore.ai,https://app.manacore.ai + ports: + - "127.0.0.1:3016:3016" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3016/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + calendar-web: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/calendar-web:${CALENDAR_WEB_VERSION:-latest} + container_name: calendar-web + restart: always + depends_on: + calendar-backend: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 5186 + PUBLIC_BACKEND_URL: http://calendar-backend:3016 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + PUBLIC_BACKEND_URL_CLIENT: https://calendar-api.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.manacore.ai + ports: + - "127.0.0.1:5186:5186" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5186/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + # ============================================ + # Clock App + # ============================================ + + clock-backend: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/clock-backend:${CLOCK_VERSION:-latest} + container_name: clock-backend + restart: always + depends_on: + mana-core-auth: + condition: service_healthy + postgres: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3017 + DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/clock + DB_HOST: postgres + DB_PORT: 5432 + DB_USER: ${POSTGRES_USER:-postgres} + MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + CORS_ORIGINS: https://clock.manacore.ai,https://app.manacore.ai + ports: + - "127.0.0.1:3017:3017" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3017/api/v1/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + clock-web: + image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/clock-web:${CLOCK_WEB_VERSION:-latest} + container_name: clock-web + restart: always + depends_on: + clock-backend: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 5187 + PUBLIC_BACKEND_URL: http://clock-backend:3017 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + PUBLIC_BACKEND_URL_CLIENT: https://clock-api.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.manacore.ai + ports: + - "127.0.0.1:5187:5187" + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5187/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - manacore-network + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + +# ============================================ +# Networks +# ============================================ + +networks: + manacore-network: + driver: bridge + name: manacore-production + +# ============================================ +# Volumes +# ============================================ + +volumes: + postgres_data: + name: manacore-postgres-prod + redis_data: + name: manacore-redis-prod + caddy_data: + name: manacore-caddy-data + caddy_config: + name: manacore-caddy-config diff --git a/docker/production/restore.sh b/docker/production/restore.sh new file mode 100755 index 000000000..0ac759ce5 --- /dev/null +++ b/docker/production/restore.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# ManaCore PostgreSQL Restore Script +# +# Usage: +# ./restore.sh chat backups/chat_20250116_030000.sql.gz +# +# WARNING: This will OVERWRITE the existing database! + +set -euo pipefail + +# Configuration +CONTAINER_NAME="${CONTAINER_NAME:-manacore-postgres}" +POSTGRES_USER="${POSTGRES_USER:-postgres}" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log_info() { + echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +# Validate arguments +if [ $# -ne 2 ]; then + echo "Usage: $0 " + echo "Example: $0 chat backups/chat_20250116_030000.sql.gz" + exit 1 +fi + +DB_NAME=$1 +BACKUP_FILE=$2 + +# Check if backup file exists +if [ ! -f "$BACKUP_FILE" ]; then + log_error "Backup file not found: $BACKUP_FILE" + exit 1 +fi + +# Confirm restoration +echo "" +log_warn "WARNING: This will OVERWRITE the database '$DB_NAME'!" +log_warn "Backup file: $BACKUP_FILE" +echo "" +read -p "Are you sure you want to continue? (yes/no): " confirm + +if [ "$confirm" != "yes" ]; then + log_info "Restore cancelled." + exit 0 +fi + +log_info "=== ManaCore Restore Started ===" + +# Create backup of current state before restore +TIMESTAMP=$(date '+%Y%m%d_%H%M%S') +CURRENT_BACKUP="./backups/${DB_NAME}_pre_restore_${TIMESTAMP}.sql.gz" + +log_info "Creating backup of current state..." +docker exec "$CONTAINER_NAME" pg_dump -U "$POSTGRES_USER" "$DB_NAME" 2>/dev/null | gzip > "$CURRENT_BACKUP" || true +log_info "Current state backed up to: $CURRENT_BACKUP" + +# Drop and recreate database +log_info "Dropping and recreating database: $DB_NAME" +docker exec "$CONTAINER_NAME" psql -U "$POSTGRES_USER" -c "DROP DATABASE IF EXISTS $DB_NAME;" +docker exec "$CONTAINER_NAME" psql -U "$POSTGRES_USER" -c "CREATE DATABASE $DB_NAME;" + +# Restore from backup +log_info "Restoring from backup..." +gunzip -c "$BACKUP_FILE" | docker exec -i "$CONTAINER_NAME" psql -U "$POSTGRES_USER" "$DB_NAME" + +log_info "=== ManaCore Restore Completed ===" +log_info "Database '$DB_NAME' has been restored from $BACKUP_FILE" +log_info "Previous state saved to: $CURRENT_BACKUP"