|
| 1 | +#!/bin/bash |
| 2 | +# MetaboFlow Deployment Script |
| 3 | +# Run from the MetaboFlow repo root: bash deploy/deploy.sh |
| 4 | +set -euo pipefail |
| 5 | + |
| 6 | +DOMAIN="${METABOFLOW_DOMAIN:-ponytech.dev}" |
| 7 | +ADMIN_EMAIL="${ADMIN_EMAIL:-jiajunagent@gmail.com}" |
| 8 | +SECRET_KEY=$(openssl rand -hex 32) |
| 9 | + |
| 10 | +echo "=== MetaboFlow Deployment ===" |
| 11 | +echo "Domain: $DOMAIN" |
| 12 | +echo "Admin email: $ADMIN_EMAIL" |
| 13 | + |
| 14 | +# 1. Create production .env |
| 15 | +echo ">>> Step 1: Create .env" |
| 16 | +cat > .env << EOF |
| 17 | +# MetaboFlow Production Environment |
| 18 | +FRONTEND_PORT=3005 |
| 19 | +METABOFLOW_DATABASE_URL=postgresql://metaboflow:metaboflow@postgres:5432/metaboflow |
| 20 | +METABOFLOW_REDIS_URL=redis://redis:6379/0 |
| 21 | +METABOFLOW_CELERY_BROKER_URL=redis://redis:6379/0 |
| 22 | +METABOFLOW_CELERY_RESULT_BACKEND=redis://redis:6379/1 |
| 23 | +METABOFLOW_SECRET_KEY=${SECRET_KEY} |
| 24 | +METABOFLOW_CORS_ORIGINS=["http://localhost:3005","http://${DOMAIN}","https://${DOMAIN}"] |
| 25 | +METABOFLOW_XCMS_WORKER_URL=http://xcms-worker:8001 |
| 26 | +METABOFLOW_STATS_WORKER_URL=http://stats-worker:8002 |
| 27 | +METABOFLOW_CHART_SERVICE_URL=http://chart-service:8005 |
| 28 | +METABOFLOW_ANNOT_WORKER_URL=http://annot-worker:8006 |
| 29 | +METABOFLOW_SIRIUS_WORKER_URL=http://sirius-worker:8007 |
| 30 | +METABOFLOW_CHART_R_WORKER_URL=http://chart-r-worker:8008 |
| 31 | +METABOFLOW_REPORT_WORKER_URL=http://report-worker:8009 |
| 32 | +POSTGRES_USER=metaboflow |
| 33 | +POSTGRES_PASSWORD=metaboflow |
| 34 | +POSTGRES_DB=metaboflow |
| 35 | +EOF |
| 36 | +echo ".env created (SECRET_KEY generated)" |
| 37 | + |
| 38 | +# 2. Build all images |
| 39 | +echo ">>> Step 2: Build Docker images (this takes 15-30 minutes first time)" |
| 40 | +docker compose build --parallel 2>&1 | tail -20 |
| 41 | + |
| 42 | +# 3. Start all services |
| 43 | +echo ">>> Step 3: Start services" |
| 44 | +docker compose up -d |
| 45 | + |
| 46 | +# 4. Wait for services to be healthy |
| 47 | +echo ">>> Step 4: Waiting for services..." |
| 48 | +for i in $(seq 1 60); do |
| 49 | + healthy=$(docker compose ps --format json 2>/dev/null | python3 -c " |
| 50 | +import sys, json |
| 51 | +lines = sys.stdin.read().strip().split('\n') |
| 52 | +total = len(lines) |
| 53 | +ok = sum(1 for l in lines if 'healthy' in l or 'running' in l.lower()) |
| 54 | +print(f'{ok}/{total}') |
| 55 | +" 2>/dev/null || echo "?/?") |
| 56 | + echo " [$i/60] Services: $healthy" |
| 57 | + if docker compose ps | grep -q "unhealthy\|starting"; then |
| 58 | + sleep 10 |
| 59 | + else |
| 60 | + break |
| 61 | + fi |
| 62 | +done |
| 63 | + |
| 64 | +# 5. Create admin user + invite codes |
| 65 | +echo ">>> Step 5: Create admin user" |
| 66 | +docker compose exec -T backend uv run python3 -c " |
| 67 | +from app.db.base import SessionLocal, init_db |
| 68 | +from app.db.models import User, InviteCode |
| 69 | +from app.services.auth_service import hash_password |
| 70 | +import uuid, secrets |
| 71 | +from datetime import datetime, timedelta, timezone |
| 72 | +
|
| 73 | +init_db() |
| 74 | +session = SessionLocal() |
| 75 | +
|
| 76 | +# Admin user |
| 77 | +existing = session.query(User).filter_by(email='${ADMIN_EMAIL}').first() |
| 78 | +if not existing: |
| 79 | + admin = User(id=str(uuid.uuid4()), email='${ADMIN_EMAIL}', |
| 80 | + password_hash=hash_password('MetaboFlow2026!'), |
| 81 | + is_admin=True, created_at=datetime.now(timezone.utc)) |
| 82 | + session.add(admin) |
| 83 | + print(f'Admin created: ${ADMIN_EMAIL} / MetaboFlow2026!') |
| 84 | +else: |
| 85 | + print('Admin already exists') |
| 86 | +
|
| 87 | +# Generate 10 invite codes |
| 88 | +codes = [] |
| 89 | +for i in range(10): |
| 90 | + code = secrets.token_urlsafe(16) |
| 91 | + invite = InviteCode(id=str(uuid.uuid4()), code=code, |
| 92 | + expires_at=datetime.now(timezone.utc) + timedelta(days=90), |
| 93 | + created_at=datetime.now(timezone.utc)) |
| 94 | + session.add(invite) |
| 95 | + codes.append(code) |
| 96 | +
|
| 97 | +session.commit() |
| 98 | +session.close() |
| 99 | +
|
| 100 | +print(f'\n10 invite codes (valid 90 days):') |
| 101 | +for i, c in enumerate(codes, 1): |
| 102 | + print(f' {i:2d}. {c}') |
| 103 | +" |
| 104 | + |
| 105 | +# 6. Test health |
| 106 | +echo "" |
| 107 | +echo ">>> Step 6: Health check" |
| 108 | +echo -n "Backend: " |
| 109 | +curl -s http://localhost:8000/health | python3 -c "import sys,json; print(json.load(sys.stdin)['status'])" 2>/dev/null || echo "FAIL" |
| 110 | +echo -n "Frontend: " |
| 111 | +curl -s -o /dev/null -w "%{http_code}" http://localhost:3005/ 2>/dev/null || echo "FAIL" |
| 112 | + |
| 113 | +echo "" |
| 114 | +echo "==============================================" |
| 115 | +echo " MetaboFlow deployed successfully!" |
| 116 | +echo "==============================================" |
| 117 | +echo "" |
| 118 | +echo " Frontend: http://${DOMAIN}:3005" |
| 119 | +echo " API docs: http://${DOMAIN}:8000/docs" |
| 120 | +echo "" |
| 121 | +echo " Admin login:" |
| 122 | +echo " Email: ${ADMIN_EMAIL}" |
| 123 | +echo " Password: MetaboFlow2026!" |
| 124 | +echo "" |
| 125 | +echo " Change admin password after first login!" |
| 126 | +echo "" |
| 127 | +echo " Next: setup Nginx reverse proxy + SSL" |
| 128 | +echo " bash deploy/setup-nginx.sh" |
| 129 | +echo "==============================================" |
0 commit comments