mirror of
https://github.com/Heretek-AI/job-bot.git
synced 2026-11-07 10:55:57 -10:00
278 lines
10 KiB
YAML
278 lines
10 KiB
YAML
name: job-bot
|
|
|
|
# ==============================================================================
|
|
# Prepackaged, Zero-Build Docker Compose Stack
|
|
# All containers are prebuilt by GitHub Actions CI to ghcr.io/heretek-ai/job-bot
|
|
# Remote usage:
|
|
# 1. cp .env.example .env && nano .env
|
|
# 2. docker compose pull && docker compose up -d
|
|
# ==============================================================================
|
|
|
|
services:
|
|
# ============================================================================
|
|
# 1. PostgreSQL 16 (Prepackaged with ATS Password Vault & IMAP Sync schema)
|
|
# ============================================================================
|
|
postgres:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/postgres:${IMAGE_TAG:-latest}
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${POSTGRES_USER:-jobpilot}
|
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-jobpilot_secret_password}
|
|
POSTGRES_DB: ${POSTGRES_DB:-jobpilot}
|
|
volumes:
|
|
- postgres-data:/var/lib/postgresql/data
|
|
networks:
|
|
- app-net
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-jobpilot} -d ${POSTGRES_DB:-jobpilot}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
|
|
# ============================================================================
|
|
# 2. Automatic Prisma Database Schema Migration (JobPilot API)
|
|
# ============================================================================
|
|
db-migrate:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/api:${IMAGE_TAG:-latest}
|
|
env_file: .env
|
|
environment:
|
|
DATABASE_URL: ${DATABASE_URL:-postgresql://${POSTGRES_USER:-jobpilot}:${POSTGRES_PASSWORD:-jobpilot_secret_password}@postgres:5432/${POSTGRES_DB:-jobpilot}}
|
|
networks:
|
|
- app-net
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
command:
|
|
- "sh"
|
|
- "-c"
|
|
- |
|
|
HAS_MIGRATIONS=$$(psql "$$DATABASE_URL" -tAc "SELECT to_regclass('public._prisma_migrations');" 2>/dev/null | tr -d ' \r\n' || true)
|
|
if [ -z "$$HAS_MIGRATIONS" ]; then
|
|
psql "$$DATABASE_URL" -v ON_ERROR_STOP=1 -c "DROP TABLE IF EXISTS public.ats_credentials, public.imap_processed_emails CASCADE;"
|
|
fi
|
|
exec bun run db:migrate:apply
|
|
restart: "no"
|
|
|
|
# ============================================================================
|
|
# 3. Vault Schema Initialization (Runs after Prisma push so tables are preserved)
|
|
# ============================================================================
|
|
vault-init:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/postgres:${IMAGE_TAG:-latest}
|
|
environment:
|
|
PGPASSWORD: ${POSTGRES_PASSWORD:-jobpilot_secret_password}
|
|
networks:
|
|
- app-net
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
command:
|
|
- "psql"
|
|
- "-h"
|
|
- "postgres"
|
|
- "-U"
|
|
- "${POSTGRES_USER:-jobpilot}"
|
|
- "-d"
|
|
- "${POSTGRES_DB:-jobpilot}"
|
|
- "-v"
|
|
- "ON_ERROR_STOP=1"
|
|
- "-f"
|
|
- "/usr/local/share/jobbot/init-vault.sql"
|
|
restart: "no"
|
|
|
|
# ============================================================================
|
|
# 4. JobPilot API Backend (Bun + Elysia + Prisma)
|
|
# ============================================================================
|
|
api:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/api:${IMAGE_TAG:-latest}
|
|
restart: unless-stopped
|
|
env_file: .env
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: 4101
|
|
DATABASE_URL: ${DATABASE_URL:-postgresql://${POSTGRES_USER:-jobpilot}:${POSTGRES_PASSWORD:-jobpilot_secret_password}@postgres:5432/${POSTGRES_DB:-jobpilot}}
|
|
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:8080,http://127.0.0.1:8080,http://localhost:4100,http://web:4100}
|
|
APP_URL: ${APP_URL:-http://localhost:8080}
|
|
ports:
|
|
- "127.0.0.1:${API_PORT:-4101}:4101"
|
|
volumes:
|
|
- api-storage:/app/apps/api/storage
|
|
- shared-workspace:/app/shared
|
|
networks:
|
|
- app-net
|
|
depends_on:
|
|
postgres:
|
|
condition: service_healthy
|
|
vault-init:
|
|
condition: service_completed_successfully
|
|
command:
|
|
- "sh"
|
|
- "-c"
|
|
- |
|
|
for var in SUPER_ADMIN_EMAIL GOOGLE_CLIENT_ID GOOGLE_CLIENT_SECRET GITHUB_CLIENT_ID GITHUB_CLIENT_SECRET RESEND_API_KEY VAPID_PUBLIC_KEY VAPID_PRIVATE_KEY VAPID_SUBJECT; do
|
|
eval "val=\$${var}"
|
|
if [ -z "$$val" ]; then unset "$$var"; fi
|
|
done
|
|
KEY_LEN=0
|
|
case "$${SECRET_MASTER_KEY:-}" in
|
|
""|REPLACE_*) KEY_LEN=0 ;;
|
|
*) KEY_LEN=$$(printf "%s" "$$SECRET_MASTER_KEY" | openssl base64 -d -A 2>/dev/null | wc -c | tr -d ' ') ;;
|
|
esac
|
|
if [ "$$KEY_LEN" != "32" ]; then
|
|
mkdir -p /app/apps/api/storage
|
|
if [ ! -s /app/apps/api/storage/.secret_master_key ]; then
|
|
openssl rand -base64 32 | tr -d '\r\n' > /app/apps/api/storage/.secret_master_key
|
|
fi
|
|
export SECRET_MASTER_KEY=$$(cat /app/apps/api/storage/.secret_master_key | tr -d '\r\n')
|
|
echo "[api] Auto-loaded persistent 32-byte SECRET_MASTER_KEY from storage"
|
|
fi
|
|
bun run db:seed || echo "[api] Warning: db:seed returned non-zero, continuing..."
|
|
exec bun run start
|
|
healthcheck:
|
|
test: ["CMD", "bun", "-e", "fetch('http://localhost:4101/api/health').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
|
|
interval: 20s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
# ============================================================================
|
|
# 5. JobPilot Web UI (Prepackaged & Origin-Agnostic Next.js Standalone)
|
|
# ============================================================================
|
|
web:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/web:${IMAGE_TAG:-latest}
|
|
restart: unless-stopped
|
|
environment:
|
|
NODE_ENV: production
|
|
PORT: 4100
|
|
HOSTNAME: 0.0.0.0
|
|
INTERNAL_API_URL: http://api:4101
|
|
ports:
|
|
- "127.0.0.1:${WEB_PORT:-4100}:4100"
|
|
networks:
|
|
- app-net
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "node", "-e", "fetch('http://localhost:4100').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))"]
|
|
interval: 20s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
|
|
# ============================================================================
|
|
# 6. Stealthy Auto-Browse: Camoufox + OS-Level Input + noVNC (:5900)
|
|
# ============================================================================
|
|
stealth-browser:
|
|
image: psyb0t/stealthy-auto-browse:latest
|
|
container_name: job-bot-stealth-browser-1
|
|
hostname: stealth-browser
|
|
restart: unless-stopped
|
|
shm_size: "2gb"
|
|
environment:
|
|
HTTP_LISTEN_PORT: 8080
|
|
VNC_LISTEN_PORT: 5900
|
|
VNC_SHOW_CURSOR: "true"
|
|
XVFB_RESOLUTION: ${XVFB_RESOLUTION:-1280x800}
|
|
TZ: ${TZ:-America/New_York}
|
|
PROXY_URL: ${BROWSER_PROXY_URL:-}
|
|
ports:
|
|
- "${VNC_PORT:-5900}:5900"
|
|
volumes:
|
|
- stealth-browser-data:/userdata
|
|
- shared-workspace:/app/shared
|
|
- api-storage:/app/apps/api/storage:ro
|
|
- terminal-data:/root/.jobpilot:ro
|
|
networks:
|
|
- browser-net
|
|
- app-net
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
# ============================================================================
|
|
# 7. Prepackaged Autonomous Terminal Worker
|
|
# ============================================================================
|
|
terminal:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/terminal:${IMAGE_TAG:-latest}
|
|
restart: unless-stopped
|
|
shm_size: "1gb"
|
|
env_file: .env
|
|
environment:
|
|
DOTNET_EnableWriteXorExecute: "0"
|
|
PUBLIC_PORT: ${PUBLIC_PORT:-8080}
|
|
JOBPILOT_API: http://api:4101
|
|
JOBPILOT_WEB: http://web:4100
|
|
JOBPILOT_API_TOKEN: ${JOBPILOT_API_TOKEN:-}
|
|
DATABASE_URL: ${DATABASE_URL:-postgresql://${POSTGRES_USER:-jobpilot}:${POSTGRES_PASSWORD:-jobpilot_secret_password}@postgres:5432/${POSTGRES_DB:-jobpilot}}
|
|
STEALTH_BROWSER_URL: http://stealth-browser:8080
|
|
ANTHROPIC_BASE_URL: ${ANTHROPIC_BASE_URL:-https://opencode.ai/inference/anthropic}
|
|
ANTHROPIC_AUTH_TOKEN: ${ANTHROPIC_AUTH_TOKEN}
|
|
ANTHROPIC_DEFAULT_OPUS_MODEL: ${ANTHROPIC_DEFAULT_OPUS_MODEL:-claude-opus-5-5}
|
|
ANTHROPIC_DEFAULT_SONNET_MODEL: ${ANTHROPIC_DEFAULT_SONNET_MODEL:-claude-sonnet-5-5}
|
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: ${ANTHROPIC_DEFAULT_HAIKU_MODEL:-claude-haiku-5-5}
|
|
ports:
|
|
- "127.0.0.1:${TERMINAL_PORT:-4102}:4102"
|
|
volumes:
|
|
- terminal-data:/root/.jobpilot
|
|
- claude-data:/root/.claude
|
|
- api-storage:/app/apps/api/storage
|
|
- shared-workspace:/app/shared
|
|
networks:
|
|
- app-net
|
|
- browser-net
|
|
depends_on:
|
|
api:
|
|
condition: service_healthy
|
|
stealth-browser:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "curl -fsS http://localhost:4102/healthz || exit 1"]
|
|
interval: 20s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 25s
|
|
|
|
# ============================================================================
|
|
# 8. Prepackaged Nginx Single-Origin Reverse Proxy (:8080)
|
|
# ============================================================================
|
|
nginx:
|
|
image: ghcr.io/${GHCR_REPO:-heretek-ai/job-bot}/nginx:${IMAGE_TAG:-latest}
|
|
restart: unless-stopped
|
|
ports:
|
|
- "${PUBLIC_PORT:-8080}:8080"
|
|
networks:
|
|
- app-net
|
|
depends_on:
|
|
web:
|
|
condition: service_healthy
|
|
api:
|
|
condition: service_healthy
|
|
terminal:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/api/health || exit 1"]
|
|
interval: 20s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
|
|
networks:
|
|
app-net:
|
|
driver: bridge
|
|
browser-net:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
postgres-data:
|
|
api-storage:
|
|
stealth-browser-data:
|
|
terminal-data:
|
|
claude-data:
|
|
shared-workspace:
|