diff --git a/ci/.env.prod.example b/ci/.env.prod.example deleted file mode 100644 index ed258c4..0000000 --- a/ci/.env.prod.example +++ /dev/null @@ -1,45 +0,0 @@ -# Public URL (used for CORS, email links, Caddy TLS) -# Use your domain (e.g. https://cv.example.com) or IP for testing (http://123.45.67.89) -PUBLIC_URL=https://cv.example.com -DOMAIN=cv.example.com - -# Database (use strong credentials in production) -POSTGRES_USER=cv -POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD -POSTGRES_DB=cv - -# Secrets (generate with: openssl rand -hex 32) -JWT_SECRET=CHANGE_ME_GENERATE_WITH_OPENSSL -ENCRYPTION_KEY=CHANGE_ME_GENERATE_WITH_OPENSSL_64_CHARS - -# JWT Expiry -JWT_ACCESS_TOKEN_EXPIRY=15m -JWT_REFRESH_TOKEN_EXPIRY=7d - -# AI Provider (anthropic or openai — no local llama in production) -AI_PROVIDER=anthropic -ANTHROPIC_API_KEY=sk-ant-... -# ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 - -# Or use OpenAI instead: -# AI_PROVIDER=openai -# OPENAI_API_KEY=sk-... -# OPENAI_MODEL=gpt-4o-mini - -# Email (optional — logs to console if not set) -# RESEND_API_KEY=re_... -# EMAIL_FROM_ADDRESS=noreply@example.com -# EMAIL_FROM_NAME=CV Generator - -# Worker -POLL_INTERVAL_MS=2000 -PDF_TIMEOUT_MS=30000 -HEARTBEAT_DB_INTERVAL_MS=30000 - -# Chromium launch args (comma-separated) -# Default (dev): --no-sandbox,--disable-dev-shm-usage -# Low-memory: --no-sandbox,--disable-dev-shm-usage,--disable-gpu,--single-process,--disable-extensions -CHROMIUM_ARGS=--no-sandbox,--disable-dev-shm-usage,--disable-gpu,--single-process,--disable-extensions - -# project-q (path on the Droplet after setup) -PROJECT_Q_PATH=/opt/cv-generator/project-q diff --git a/ci/Caddyfile b/ci/Caddyfile deleted file mode 100644 index a659637..0000000 --- a/ci/Caddyfile +++ /dev/null @@ -1,33 +0,0 @@ -{$DOMAIN:localhost} { - @protected { - not path /health - } - - basic_auth @protected { - {$BASIC_AUTH_USER:admin} {$BASIC_AUTH_HASH} - } - - handle /graphql { - reverse_proxy server:3000 - } - - handle /health { - reverse_proxy server:3000 - } - - handle /api/* { - reverse_proxy server:3000 - } - - handle { - reverse_proxy client:80 - } -} - -docs.{$DOMAIN:localhost} { - basic_auth { - {$BASIC_AUTH_USER:admin} {$BASIC_AUTH_HASH} - } - - reverse_proxy docs:80 -} diff --git a/ci/deploy.sh b/ci/deploy.sh deleted file mode 100755 index 3de98e7..0000000 --- a/ci/deploy.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Deploy CV Generator to production -# Run from the repo root: ci/deploy.sh - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(dirname "$SCRIPT_DIR")" -ENV_FILE="$REPO_DIR/.env.prod" -COMPOSE_FILE="$REPO_DIR/docker-compose.prod.yml" - -cd "$REPO_DIR" - -if [ ! -f "$ENV_FILE" ]; then - echo "Error: $ENV_FILE not found. Copy ci/.env.prod.example to .env.prod and configure it." - exit 1 -fi - -# Export env vars for Caddy / compose -set -a -source "$ENV_FILE" -set +a - -echo "==> Pulling latest code" -git pull --ff-only - -echo "==> Rebuilding project-q (if needed)" -if [ -d "$PROJECT_Q_PATH" ]; then - (cd "$PROJECT_Q_PATH" && git pull --ff-only && npm install && npm run build) -else - echo "Warning: PROJECT_Q_PATH=$PROJECT_Q_PATH not found. Build may fail." -fi - -echo "==> Regenerating Docker manifests" -sh .docker/copy-manifests.sh - -echo "==> Building images" -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" build - -echo "==> Running database migrations" -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" run --rm api \ - sh -c "cd /app/packages/core && npx prisma migrate deploy" - -echo "==> Starting services" -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" up -d - -echo "==> Seeding database (idempotent)" -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec api \ - node dist/scripts/seed.js - -echo "==> Cleaning up old images" -docker image prune -f - -echo "" -echo "==> Deploy complete!" -docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" ps -echo "" -echo "Run 'docker compose -f $COMPOSE_FILE --env-file $ENV_FILE logs -f' to watch logs" diff --git a/ci/docker-compose.droplet.yml b/ci/docker-compose.droplet.yml deleted file mode 100644 index ea60865..0000000 --- a/ci/docker-compose.droplet.yml +++ /dev/null @@ -1,115 +0,0 @@ -services: - caddy: - image: caddy:2-alpine - environment: - DOMAIN: ${DOMAIN} - ports: - - "80:80" - - "443:443" - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - caddy-data:/data - - caddy-config:/config - depends_on: - server: - condition: service_healthy - client: - condition: service_healthy - restart: unless-stopped - - db: - image: postgres:16-alpine - environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} - volumes: - - db-data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] - interval: 10s - timeout: 5s - retries: 3 - restart: unless-stopped - - server: - image: cv-generator-server:latest - environment: - PORT: "3000" - NODE_ENV: production - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} - JWT_SECRET: ${JWT_SECRET} - JWT_ACCESS_TOKEN_EXPIRY: ${JWT_ACCESS_TOKEN_EXPIRY:-15m} - JWT_REFRESH_TOKEN_EXPIRY: ${JWT_REFRESH_TOKEN_EXPIRY:-7d} - ENCRYPTION_KEY: ${ENCRYPTION_KEY} - RESEND_API_KEY: ${RESEND_API_KEY:-} - EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-} - EMAIL_FROM_NAME: ${EMAIL_FROM_NAME:-CV Generator} - CLIENT_URL: ${PUBLIC_URL} - ALLOWED_ORIGINS: ${PUBLIC_URL} - AI_PROVIDER: ${AI_PROVIDER:-anthropic} - AI_TEMPERATURE: ${AI_TEMPERATURE:-0.1} - AI_MAX_TOKENS: ${AI_MAX_TOKENS:-8192} - AI_TIMEOUT: ${AI_TIMEOUT:-60000} - ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} - ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-4-5-20250929} - OPENAI_API_KEY: ${OPENAI_API_KEY:-} - OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4o-mini} - PDF_OUTPUT_DIR: /app/pdf-output - depends_on: - db: - condition: service_healthy - volumes: - - worker-output:/app/pdf-output:ro - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3000/health"] - interval: 15s - timeout: 5s - retries: 3 - start_period: 30s - restart: unless-stopped - - client: - image: cv-generator-client:latest - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:80"] - interval: 15s - timeout: 5s - retries: 3 - start_period: 5s - restart: unless-stopped - - worker: - image: cv-generator-worker:latest - environment: - DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} - QUEUE_SCHEMA: ${QUEUE_SCHEMA:-queue} - QUEUE_NAME: ${QUEUE_NAME:-default} - POLL_INTERVAL_MS: ${POLL_INTERVAL_MS:-2000} - PDF_OUTPUT_DIR: /app/pdf-output - PDF_TIMEOUT_MS: ${PDF_TIMEOUT_MS:-30000} - HEARTBEAT_FILE_PATH: /tmp/worker-heartbeat - HEARTBEAT_DB_INTERVAL_MS: ${HEARTBEAT_DB_INTERVAL_MS:-30000} - CHROMIUM_ARGS: ${CHROMIUM_ARGS:---no-sandbox,--disable-dev-shm-usage,--disable-gpu,--single-process,--disable-extensions} - depends_on: - db: - condition: service_healthy - volumes: - - worker-output:/app/pdf-output - deploy: - resources: - limits: - memory: 512M - healthcheck: - test: ["CMD-SHELL", "find /tmp/worker-heartbeat -mmin -1 | grep -q ."] - interval: 30s - timeout: 5s - retries: 3 - start_period: 15s - restart: unless-stopped - -volumes: - db-data: - worker-output: - caddy-data: - caddy-config: diff --git a/ci/setup-droplet.sh b/ci/setup-droplet.sh deleted file mode 100755 index 121349e..0000000 --- a/ci/setup-droplet.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# One-time Droplet setup for CV Generator -# Run as root on a fresh Ubuntu 24.04 Droplet: -# curl -sSL | bash -# Or: ssh root@droplet 'bash -s' < ci/setup-droplet.sh - -DEPLOY_DIR="/opt/cv-generator" -DEPLOY_USER="deploy" - -echo "==> Installing Docker" -apt-get update -apt-get install -y ca-certificates curl gnupg -install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg -chmod a+r /etc/apt/keyrings/docker.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null -apt-get update -apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - -echo "==> Configuring firewall" -ufw allow OpenSSH -ufw allow 80/tcp -ufw allow 443/tcp -ufw --force enable - -echo "==> Creating deploy user" -if ! id "$DEPLOY_USER" &>/dev/null; then - adduser --disabled-password --gecos "" "$DEPLOY_USER" - usermod -aG docker "$DEPLOY_USER" -fi - -echo "==> Creating deploy directory" -mkdir -p "$DEPLOY_DIR" -chown "$DEPLOY_USER:$DEPLOY_USER" "$DEPLOY_DIR" - -echo "==> Cloning project-q (build dependency)" -su - "$DEPLOY_USER" -c " - cd $DEPLOY_DIR - if [ ! -d project-q ]; then - git clone https://github.com/riotbyte/project-q.git project-q - fi - cd project-q - npm install - npm run build -" - -echo "==> Cloning cv-generator" -su - "$DEPLOY_USER" -c " - cd $DEPLOY_DIR - if [ ! -d app ]; then - git clone https://github.com/mokkenstorm-dev/cv-generator.git app - fi -" - -echo "==> Creating .env.prod" -if [ ! -f "$DEPLOY_DIR/app/.env.prod" ]; then - cp "$DEPLOY_DIR/app/ci/.env.prod.example" "$DEPLOY_DIR/app/.env.prod" - # Generate secrets - JWT_SECRET=$(openssl rand -hex 32) - ENCRYPTION_KEY=$(openssl rand -hex 32) - DB_PASSWORD=$(openssl rand -hex 16) - sed -i "s|CHANGE_ME_GENERATE_WITH_OPENSSL_64_CHARS|$ENCRYPTION_KEY|" "$DEPLOY_DIR/app/.env.prod" - sed -i "s|CHANGE_ME_GENERATE_WITH_OPENSSL|$JWT_SECRET|" "$DEPLOY_DIR/app/.env.prod" - sed -i "s|CHANGE_ME_STRONG_PASSWORD|$DB_PASSWORD|" "$DEPLOY_DIR/app/.env.prod" - - # Generate basic auth hash (Caddy bcrypt format) - BASIC_AUTH_PASS=$(openssl rand -hex 8) - BASIC_AUTH_HASH=$(docker run --rm caddy:2-alpine caddy hash-password --plaintext "$BASIC_AUTH_PASS") - sed -i "s|CHANGE_ME_CADDY_HASH|$BASIC_AUTH_HASH|" "$DEPLOY_DIR/app/.env.prod" - echo "" - echo " Generated secrets in $DEPLOY_DIR/app/.env.prod" - echo " Basic auth password: $BASIC_AUTH_PASS (user: admin)" - echo " Edit this file to set your domain, AI keys, etc." -fi - -echo "" -echo "==> Setup complete!" -echo "" -echo "Next steps:" -echo " 1. Edit $DEPLOY_DIR/app/.env.prod (set DOMAIN, PUBLIC_URL, API keys)" -echo " 2. Run: cd $DEPLOY_DIR/app && ci/deploy.sh" -echo " 3. Point your DNS A record to this Droplet's IP" -echo ""