diff --git a/docs/STAGING_SETUP.md b/docs/STAGING_SETUP.md new file mode 100644 index 000000000..0ae580b97 --- /dev/null +++ b/docs/STAGING_SETUP.md @@ -0,0 +1,441 @@ +# Staging Environment Setup Guide + +This document describes the complete staging environment setup for ManaCore apps on Hetzner VPS with HTTPS via Caddy reverse proxy. + +## Overview + +| Component | Details | +|-----------|---------| +| **Server** | Hetzner VPS (46.224.108.214) | +| **Domain** | manacore.ai (Namecheap) | +| **Reverse Proxy** | Caddy (auto-SSL via Let's Encrypt) | +| **Container Runtime** | Docker Compose | +| **SSH Access** | `ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214` | + +## Architecture + +``` + ┌─────────────────────────────────────────────┐ + │ Hetzner VPS (46.224.108.214) │ + │ │ + Internet │ ┌─────────────────────────────────────┐ │ + │ │ │ Caddy (ports 80/443) │ │ + │ │ │ Auto-SSL via Let's Encrypt │ │ + ▼ │ └──────────────┬──────────────────────┘ │ +┌──────────────┐ │ │ │ +│ Namecheap │ │ ▼ │ +│ DNS Records │────────────────────│ ┌─────────────────────────────────────┐ │ +│ │ │ │ Docker Compose Services │ │ +│ *.staging │ │ │ │ │ +│ A → IP │ │ │ mana-core-auth:3001 │ │ +└──────────────┘ │ │ chat-web:3000 / chat-backend:3002 │ │ + │ │ clock-web:5187 / clock-backend:3017│ │ + │ │ calendar-web:5186 / calendar-api:3016│ │ + │ │ todo-web:5188 / todo-backend:3018 │ │ + │ │ manacore-web:5173 │ │ + │ │ postgres:5432 / redis:6379 │ │ + │ └─────────────────────────────────────┘ │ + └─────────────────────────────────────────────┘ +``` + +## Domain Mapping + +### DNS Configuration (Namecheap) + +| Type | Host | Value | TTL | +|------|------|-------|-----| +| A | `staging` | 46.224.108.214 | Automatic | +| A | `*.staging` | 46.224.108.214 | Automatic | + +The wildcard record `*.staging` enables all subdomains like `auth.staging.manacore.ai`, `clock.staging.manacore.ai`, etc. + +### Staging URLs + +| Service | URL | Internal Port | +|---------|-----|---------------| +| **Auth** | https://auth.staging.manacore.ai | 3001 | +| **ManaCore Web** | https://staging.manacore.ai | 5173 | +| **Chat Web** | https://chat.staging.manacore.ai | 3000 | +| **Chat API** | https://chat-api.staging.manacore.ai | 3002 | +| **Clock Web** | https://clock.staging.manacore.ai | 5187 | +| **Clock API** | https://clock-api.staging.manacore.ai | 3017 | +| **Calendar Web** | https://calendar.staging.manacore.ai | 5186 | +| **Calendar API** | https://calendar-api.staging.manacore.ai | 3016 | +| **Todo Web** | https://todo.staging.manacore.ai | 5188 | +| **Todo API** | https://todo-api.staging.manacore.ai | 3018 | + +## Caddy Reverse Proxy + +### Installation (One-time setup) + +```bash +# SSH into server +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 + +# Create Caddy data directory +mkdir -p ~/caddy_data ~/caddy_config + +# Run Caddy container +docker run -d \ + --name caddy \ + --network host \ + --restart unless-stopped \ + -v ~/Caddyfile:/etc/caddy/Caddyfile \ + -v ~/caddy_data:/data \ + -v ~/caddy_config:/config \ + caddy:2-alpine +``` + +### Configuration + +The Caddyfile is stored at: +- **Server**: `~/Caddyfile` +- **Repo**: `docker/caddy/Caddyfile.staging` + +```caddyfile +# ManaCore Staging Reverse Proxy + +auth.staging.manacore.ai { + reverse_proxy localhost:3001 +} + +chat.staging.manacore.ai { + reverse_proxy localhost:3000 +} + +chat-api.staging.manacore.ai { + reverse_proxy localhost:3002 +} + +staging.manacore.ai { + reverse_proxy localhost:5173 +} + +calendar.staging.manacore.ai { + reverse_proxy localhost:5186 +} + +calendar-api.staging.manacore.ai { + reverse_proxy localhost:3016 +} + +clock.staging.manacore.ai { + reverse_proxy localhost:5187 +} + +clock-api.staging.manacore.ai { + reverse_proxy localhost:3017 +} + +todo.staging.manacore.ai { + reverse_proxy localhost:5188 +} + +todo-api.staging.manacore.ai { + reverse_proxy localhost:3018 +} +``` + +### Updating Caddy Configuration + +```bash +# Copy updated config to server +scp -i ~/.ssh/hetzner_deploy_key docker/caddy/Caddyfile.staging deploy@46.224.108.214:~/Caddyfile + +# Reload Caddy (no downtime) +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 "docker exec caddy caddy reload --config /etc/caddy/Caddyfile" +``` + +### Caddy Management Commands + +```bash +# View logs +docker logs caddy -f + +# Restart Caddy +docker restart caddy + +# Check Caddy status +docker exec caddy caddy validate --config /etc/caddy/Caddyfile +``` + +## SvelteKit Runtime Environment Variables + +### The Problem + +SvelteKit's `$env/static/public` variables are replaced at **build time**. When Docker images are built in CI, the environment variables are baked into the JavaScript bundles. This means containers cannot use different URLs for different environments. + +### The Solution + +Use `$env/dynamic/private` in `hooks.server.ts` to read environment variables at **runtime**, then inject them into the HTML for client-side access. + +### Implementation + +Each SvelteKit web app has a `hooks.server.ts` that: +1. Reads `_CLIENT` environment variables at runtime +2. Injects them into the HTML via ``; + return html.replace('
', `${envScript}`); + }, + }); +}; +``` + +### Environment Variable Pattern + +Each web app container receives two sets of URLs: + +| Variable | Purpose | Example | +|----------|---------|---------| +| `PUBLIC_BACKEND_URL` | Server-side (Docker network) | `http://clock-backend:3017` | +| `PUBLIC_BACKEND_URL_CLIENT` | Client-side (browser) | `https://clock-api.staging.manacore.ai` | +| `PUBLIC_MANA_CORE_AUTH_URL` | Server-side auth | `http://mana-core-auth:3001` | +| `PUBLIC_MANA_CORE_AUTH_URL_CLIENT` | Client-side auth | `https://auth.staging.manacore.ai` | + +## Docker Compose Configuration + +### File Locations + +| File | Purpose | +|------|---------| +| `docker-compose.staging.yml` | Staging configuration (repo) | +| `~/manacore-staging/docker-compose.yml` | Server deployment | + +### Key Configuration Sections + +**Web App Environment Variables:** +```yaml +clock-web: + environment: + NODE_ENV: staging + PORT: 5187 + # Server-side URLs (Docker internal network) + PUBLIC_BACKEND_URL: http://clock-backend:3017 + PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 + # Client-side URLs (browser access via HTTPS) + PUBLIC_BACKEND_URL_CLIENT: https://clock-api.staging.manacore.ai + PUBLIC_MANA_CORE_AUTH_URL_CLIENT: https://auth.staging.manacore.ai +``` + +**Backend CORS Configuration:** +```yaml +clock-backend: + environment: + CORS_ORIGINS: https://clock.staging.manacore.ai,https://staging.manacore.ai,http://localhost:5187 +``` + +**Auth Service CORS:** +```yaml +mana-core-auth: + environment: + CORS_ORIGINS: https://chat.staging.manacore.ai,https://staging.manacore.ai,https://calendar.staging.manacore.ai,https://clock.staging.manacore.ai,https://todo.staging.manacore.ai,http://localhost:3000,http://localhost:5173 +``` + +### Syncing Configuration to Server + +```bash +# Copy docker-compose to server +scp -i ~/.ssh/hetzner_deploy_key docker-compose.staging.yml deploy@46.224.108.214:~/manacore-staging/docker-compose.yml + +# Recreate containers with new config +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 "cd ~/manacore-staging && docker compose up -d --force-recreate" +``` + +## Deployment Workflow + +### CI/CD Pipeline + +The GitHub Actions workflow (`.github/workflows/cd-staging.yml`): +1. Builds Docker images on push to `dev` branch +2. Pushes images to GitHub Container Registry (ghcr.io) +3. SSHs into staging server +4. Pulls latest images +5. Restarts containers + +### Manual Deployment + +```bash +# 1. Build and push images (from local) +docker build -t ghcr.io/memo-2023/clock-web:latest -f apps/clock/apps/web/Dockerfile . +docker push ghcr.io/memo-2023/clock-web:latest + +# 2. SSH into server +ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214 + +# 3. Pull and restart +cd ~/manacore-staging +docker compose pull +docker compose up -d --force-recreate +``` + +### Updating Environment Variables + +1. Edit `docker-compose.staging.yml` locally +2. Copy to server: `scp -i ~/.ssh/hetzner_deploy_key docker-compose.staging.yml deploy@46.224.108.214:~/manacore-staging/docker-compose.yml` +3. Recreate affected containers: `docker compose up -d --force-recreate