deploy.py: --initial Modus fuer vollautomatische Erstinstallation ueber Coolify API

This commit is contained in:
Agent Zero
2026-08-03 02:05:19 +02:00
parent f775405a01
commit cd48d99c65
+307 -1
View File
@@ -679,6 +679,306 @@ def verify_only() -> int:
# ─── CLI ───────────────────────────────────────────────────────────────
# ─── Initial deployment: create all Coolify resources from scratch ──────────
# PostgreSQL Compose (pgvector for embeddings)
POSTGRES_COMPOSE = """\
services:
postgres:
image: pgvector/pgvector:pg16
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${DB_USER} -d ${DB_NAME}']
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
volumes:
pgdata:
"""
# Redis Compose
REDIS_COMPOSE = """\
services:
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
healthcheck:
test: ['CMD-SHELL', 'redis-cli ping || exit 1']
interval: 10s
timeout: 5s
retries: 5
volumes:
redisdata:
"""
# ENV variables for PostgreSQL service
POSTGRES_ENVS = [
{"key": "DB_USER", "value": "crm_user"},
{"key": "DB_PASSWORD", "value": "4B6X2wlfbIx-PyaG8kGutsatdLbjdBUI"},
{"key": "DB_NAME", "value": "crm_db"},
]
# ENV variables for Redis service
REDIS_ENVS = [
{"key": "REDIS_PASSWORD", "value": "lAjCaTf3XFP5XSaPJ1HElgLAJhQQswLT"},
]
# API Application configuration
API_GIT_REPO = os.environ.get("API_GIT_REPO", "https://forgejo.media-on.de/Leopoldadmin/leocrm.git")
API_GIT_BRANCH = os.environ.get("API_GIT_BRANCH", "main")
# API ENV variables
API_ENVS = [
{"key": "DATABASE_URL", "value": "postgresql+asyncpg://crm_api:4B6X2wlfbIx-PyaG8kGutsatdLbjdBUI@crm-postgres:5432/crm_db"},
{"key": "AUTH_DATABASE_URL", "value": "postgresql+asyncpg://crm_auth:4B6X2wlfbIx-PyaG8kGutsatdLbjdBUI@crm-postgres:5432/crm_db"},
{"key": "WORKER_DATABASE_URL", "value": "postgresql+asyncpg://crm_worker:4B6X2wlfbIx-PyaG8kGutsatdLbjdBUI@crm-postgres:5432/crm_db"},
{"key": "MIGRATION_DATABASE_URL", "value": "postgresql+asyncpg://crm_user:4B6X2wlfbIx-PyaG8kGutsatdLbjdBUI@crm-postgres:5432/crm_db"},
{"key": "REDIS_URL", "value": "redis://default:lAjCaTf3XFP5XSaPJ1HElgLAJhQQswLT@crm-redis:6379/0"},
{"key": "SECRET_KEY", "value": "vVdAnvyc-ob4myE5D1rAYn-SovzoBfQLP1z4wmWteTmFPV_lveCGIn2upNoiP590"},
{"key": "ENVIRONMENT", "value": "production"},
{"key": "STORAGE_PATH", "value": "/data/storage"},
{"key": "FRONTEND_URL", "value": "https://crm.media-on.de"},
{"key": "CORS_ORIGINS", "value": "https://crm.media-on.de"},
{"key": "SESSION_COOKIE_SECURE", "value": "true"},
{"key": "LOG_LEVEL", "value": "INFO"},
]
def create_service(client: CoolifyClient, project_uuid: str, environment_name: str,
server_uuid: str, compose_raw: str, name: str) -> str | None:
"""Create a Coolify service from a docker-compose definition.
Returns the service UUID or None on failure."""
import base64
encoded = base64.b64encode(compose_raw.encode()).decode()
try:
resp = httpx.post(
f"{client.base_url}/api/v1/services",
headers=client.headers,
json={
"project_uuid": project_uuid,
"environment_name": environment_name,
"server_uuid": server_uuid,
"docker_compose_raw": encoded,
"name": name,
},
timeout=30,
)
if resp.status_code in (200, 201):
data = resp.json()
return data.get("uuid")
print(f" Error creating service {name}: {resp.status_code} {resp.text[:200]}")
return None
except Exception as e:
print(f" Error creating service {name}: {e}")
return None
def set_service_envs(client: CoolifyClient, service_uuid: str, envs: list[dict]) -> None:
"""Set ENV variables for a service via Coolify API."""
for env in envs:
resp = httpx.post(
f"{client.base_url}/api/v1/services/{service_uuid}/envs",
headers=client.headers,
json=env,
timeout=30,
)
if resp.status_code == 409:
resp = httpx.patch(
f"{client.base_url}/api/v1/services/{service_uuid}/envs",
headers=client.headers,
json=env,
timeout=30,
)
if resp.status_code not in (200, 201):
print(f" Warning: could not set ENV {env['key']} ({resp.status_code})")
def create_api_application(client: CoolifyClient, project_uuid: str,
environment_name: str, server_uuid: str) -> str | None:
"""Create the API application from a Git repository.
Returns the application UUID or None on failure."""
try:
resp = httpx.post(
f"{client.base_url}/api/v1/applications",
headers=client.headers,
json={
"project_uuid": project_uuid,
"environment_name": environment_name,
"server_uuid": server_uuid,
"git_repository": API_GIT_REPO,
"git_branch": API_GIT_BRANCH,
"build_pack": "dockerfile",
"name": "leocrm-api",
},
timeout=30,
)
if resp.status_code in (200, 201):
data = resp.json()
return data.get("uuid")
print(f" Error creating API application: {resp.status_code} {resp.text[:300]}")
return None
except Exception as e:
print(f" Error creating API application: {e}")
return None
def deploy_initial() -> int:
"""Initial deployment: create all Coolify resources from scratch.
Creates:
1. PostgreSQL service (pgvector/pgvector:pg16)
2. Redis service (redis:7-alpine)
3. API application (from Git repo)
4. Worker service (same image as API)
5. Sets all ENV variables
6. Deploys API (builds image + runs migrations)
7. Deploys Worker
"""
print(f"\n{'='*60}")
print(" LeoCRM Initial Deployment")
print(f"{'='*60}\n")
if not COOLIFY_TOKEN:
print("ERROR: COOLIFY_API_TOKEN not set")
return 2
client = CoolifyClient(COOLIFY_BASE_URL, COOLIFY_TOKEN)
steps: list[tuple[str, StepResult]] = []
# Get project, environment, server
PROJECT_UUID = os.environ.get("COOLIFY_PROJECT_UUID", "damkcrjuy4cjofo954ahmooe")
ENVIRONMENT_NAME = os.environ.get("COOLIFY_ENVIRONMENT", "production")
SERVER_UUID = os.environ.get("COOLIFY_SERVER_UUID", "lw80w8scs444gwcw084s00s4")
# Step 1: Create PostgreSQL service
print("\n[1/7] Creating PostgreSQL service...")
pg_uuid = create_service(client, PROJECT_UUID, ENVIRONMENT_NAME, SERVER_UUID,
POSTGRES_COMPOSE, "crm-postgres")
if pg_uuid:
print(f" PostgreSQL service created: {pg_uuid[:12]}")
set_service_envs(client, pg_uuid, POSTGRES_ENVS)
# Deploy to start the container
client.deploy_application(pg_uuid)
time.sleep(10)
steps.append(("PostgreSQL service", StepResult(True, f"Created: {pg_uuid[:12]}")))
else:
steps.append(("PostgreSQL service", StepResult(False, "Failed to create")))
print_summary(steps)
return 1
_print_result(steps[-1][1])
# Step 2: Create Redis service
print("\n[2/7] Creating Redis service...")
redis_uuid = create_service(client, PROJECT_UUID, ENVIRONMENT_NAME, SERVER_UUID,
REDIS_COMPOSE, "crm-redis")
if redis_uuid:
print(f" Redis service created: {redis_uuid[:12]}")
set_service_envs(client, redis_uuid, REDIS_ENVS)
client.deploy_application(redis_uuid)
time.sleep(10)
steps.append(("Redis service", StepResult(True, f"Created: {redis_uuid[:12]}")))
else:
steps.append(("Redis service", StepResult(False, "Failed to create")))
print_summary(steps)
return 1
_print_result(steps[-1][1])
# Step 3: Create API application
print("\n[3/7] Creating API application...")
api_uuid = create_api_application(client, PROJECT_UUID, ENVIRONMENT_NAME, SERVER_UUID)
if api_uuid:
print(f" API application created: {api_uuid[:12]}")
# Set API ENV variables
for env in API_ENVS:
resp = httpx.post(
f"{client.base_url}/api/v1/applications/{api_uuid}/envs",
headers=client.headers,
json=env,
timeout=30,
)
if resp.status_code == 409:
resp = httpx.patch(
f"{client.base_url}/api/v1/applications/{api_uuid}/envs",
headers=client.headers,
json=env,
timeout=30,
)
steps.append(("API application", StepResult(True, f"Created: {api_uuid[:12]}")))
else:
steps.append(("API application", StepResult(False, "Failed to create")))
print_summary(steps)
return 1
_print_result(steps[-1][1])
# Step 4: Create Worker service
print("\n[4/7] Creating Worker service...")
worker_uuid = create_service(client, PROJECT_UUID, ENVIRONMENT_NAME, SERVER_UUID,
WORKER_COMPOSE_YAML, "leocrm-worker")
if worker_uuid:
print(f" Worker service created: {worker_uuid[:12]}")
# Set connect_to_docker_network
httpx.patch(
f"{client.base_url}/api/v1/services/{worker_uuid}",
headers=client.headers,
json={"connect_to_docker_network": True},
timeout=30,
)
set_service_envs(client, worker_uuid, WORKER_ENVS)
steps.append(("Worker service", StepResult(True, f"Created: {worker_uuid[:12]}")))
else:
steps.append(("Worker service", StepResult(False, "Failed to create")))
print_summary(steps)
return 1
_print_result(steps[-1][1])
# Step 5: Deploy API (builds image + runs migrations via prestart.sh)
print("\n[5/7] Deploying API (build + migrations)...")
r = deploy_api(client, skip_build=False)
steps.append(("API deploy", r))
_print_result(r)
if not r.success:
print_summary(steps)
return 1
# Step 6: Deploy Worker
print("\n[6/7] Deploying Worker...")
# Tag the latest API image as :latest
tag_code, tag_output = ssh_run(
'docker images --format "{{.Repository}}:{{.Tag}}" | '
'grep "^stvabl4vaqru7jclx4ittzr3:" | grep -v latest | head -1 | '
'xargs -I{} docker tag {} stvabl4vaqru7jclx4ittzr3:latest'
)
# Deploy worker
result = client.deploy_application(worker_uuid)
deploy_uuid = _extract_deploy_uuid(result)
if deploy_uuid:
dep_result = _wait_deployment(client, deploy_uuid, timeout=120)
steps.append(("Worker deploy", dep_result))
else:
wr = _wait_service_healthy(client, worker_uuid, timeout=120)
steps.append(("Worker deploy", wr))
_print_result(steps[-1][1])
# Step 7: Verification
print("\n[7/7] Running verification...")
verify_results = run_verification(client, check_worker=True)
steps.extend(verify_results)
all_ok = print_summary(steps)
return 0 if all_ok else 1
def main() -> None:
parser = argparse.ArgumentParser(
description="LeoCRM automated deployment script (Coolify API only)",
@@ -699,13 +999,19 @@ Examples:
"--worker-only", action="store_true",
help="Only deploy the worker service",
)
parser.add_argument(
"--initial", action="store_true",
help="Initial deployment: create all Coolify resources from scratch",
)
parser.add_argument(
"--verify-only", action="store_true",
help="Only run verification checks (no deployment)",
)
args = parser.parse_args()
if args.verify_only:
if args.initial:
sys.exit(deploy_initial())
elif args.verify_only:
sys.exit(verify_only())
elif args.worker_only:
sys.exit(deploy_worker_only(skip_build=args.skip_build))