From c278597757fdcfb05aff176666fa0b4e5a2a5e0d Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Wed, 5 Aug 2026 22:11:29 +0200 Subject: [PATCH] fix: deploy.py --initial als einzelner docker-compose Stack + docker-compose.yaml rename --- docker-compose.yml => docker-compose.yaml | 0 scripts/deploy.py | 245 ++++++++++++---------- 2 files changed, 129 insertions(+), 116 deletions(-) rename docker-compose.yml => docker-compose.yaml (100%) diff --git a/docker-compose.yml b/docker-compose.yaml similarity index 100% rename from docker-compose.yml rename to docker-compose.yaml diff --git a/scripts/deploy.py b/scripts/deploy.py index 124311e..7625fe4 100644 --- a/scripts/deploy.py +++ b/scripts/deploy.py @@ -401,7 +401,8 @@ def ssh_run(cmd: str, timeout: int = 60) -> tuple[int, str]: def deploy_api(client: CoolifyClient, app_uuid: str, skip_build: bool = False) -> StepResult: - """Deploy or restart the API application via Coolify API.""" + """Deploy or restart the application via Coolify API. + Uses /api/v1/deploy which works for both dockerfile and dockercompose build packs.""" # Set FQDN via PATCH if APP_DOMAIN: print(f" Setting FQDN to {APP_DOMAIN}...") @@ -410,26 +411,24 @@ def deploy_api(client: CoolifyClient, app_uuid: str, skip_build: bool = False) - except Exception as e: print(f" Warning: could not set FQDN: {e}") - if skip_build: - print(" Skip-build mode: restarting application via Coolify API...") - try: - result = client.deploy_application(app_uuid) - deploy_uuid = _extract_deploy_uuid(result) - if deploy_uuid: - print(f" Deploy queued: {deploy_uuid[:12]}") - return _wait_deployment(client, deploy_uuid, timeout=300) - return StepResult(True, "Deploy triggered (no UUID returned)") - except Exception as e: - return StepResult(False, f"Deploy trigger failed: {e}") - - print(" Triggering Coolify build & deploy for API...") + print(" Triggering Coolify deploy via /api/v1/deploy...") try: - result = client.deploy_application(app_uuid) - deploy_uuid = _extract_deploy_uuid(result) - if not deploy_uuid: - return StepResult(False, "No deployment UUID returned from Coolify") - print(f" Deploy queued: {deploy_uuid[:12]}") - return _wait_deployment(client, deploy_uuid, timeout=300) + resp = httpx.post( + f"{client.base_url}/api/v1/deploy", + headers=client.headers, + json={"uuid": app_uuid}, + timeout=30, + ) + if resp.status_code == 200: + deployments = resp.json().get("deployments", []) + if deployments: + deploy_uuid = deployments[0].get("deployment_uuid", "") + if deploy_uuid: + print(f" Deploy queued: {deploy_uuid[:12]}") + return _wait_deployment(client, deploy_uuid, timeout=600) + return StepResult(True, "Deploy triggered (no UUID returned)") + else: + return StepResult(False, f"Deploy failed: {resp.status_code} {resp.text[:200]}") except Exception as e: return StepResult(False, f"Deploy trigger failed: {e}") @@ -883,6 +882,7 @@ POSTGRES_COMPOSE = ( "services:\n" " postgres:\n" " image: pgvector/pgvector:pg16\n" + " container_name: crm-postgres\n" " restart: unless-stopped\n" " environment:\n" " POSTGRES_USER: ${DB_USER}\n" @@ -906,6 +906,7 @@ REDIS_COMPOSE = ( "services:\n" " redis:\n" " image: redis:7-alpine\n" + " container_name: crm-redis\n" " restart: unless-stopped\n" " command: redis-server --requirepass ${REDIS_PASSWORD}\n" " volumes:\n" @@ -1084,119 +1085,131 @@ def deploy_initial() -> int: environment_name = os.environ.get("COOLIFY_ENVIRONMENT", "production") server_uuid = os.environ.get("COOLIFY_SERVER_UUID", "") - # 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, get_postgres_envs()) - 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]) + # ─── Single docker-compose application (like the original working app) ── + # All 4 containers (postgres, redis, crm-app, crm-worker) in one stack. + # Coolify reads docker-compose.yaml from the Git repo, builds images, all + # containers share one network. Service names work as DNS names. No manual + # network configuration needed. No cryptic container names. - # 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, get_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_application_envs(client, api_uuid, get_api_envs()) - # Set FQDN - if APP_DOMAIN: - client.update_application(api_uuid, domains=APP_DOMAIN) - 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 (with API image reference) - print("\n[4/7] Creating Worker service...") - worker_compose = generate_worker_compose(api_uuid, "PLACEHOLDER") - # For initial creation, use a temporary compose — Coolify will assign UUID - # We'll update it after creation with the real UUID - worker_uuid = create_service(client, project_uuid, environment_name, server_uuid, - worker_compose, WORKER_NAME) - if worker_uuid: - print(f" Worker service created: {worker_uuid[:12]}") - # Update compose with real worker UUID - real_compose = generate_worker_compose(api_uuid, worker_uuid) - client.update_service(worker_uuid, real_compose) - # Set connect_to_docker_network - httpx.patch( - f"{client.base_url}/api/v1/services/{worker_uuid}", + # Step 1: Create application via private-deploy-key (with Git repo) + print("\n[1/4] Creating docker-compose application...") + try: + resp = httpx.post( + f"{client.base_url}/api/v1/applications/private-deploy-key", headers=client.headers, - json={"connect_to_docker_network": True}, + json={ + "project_uuid": project_uuid, + "environment_name": environment_name, + "server_uuid": server_uuid, + "name": APP_NAME, + "git_repository": API_GIT_REPO, + "git_branch": API_GIT_BRANCH, + "private_key_uuid": os.environ.get("COOLIFY_PRIVATE_KEY_UUID", ""), + "build_pack": "dockerfile", + "ports_exposes": "8000", + }, timeout=30, ) - set_service_envs(client, worker_uuid, get_worker_envs()) - steps.append(("Worker service", StepResult(True, f"Created: {worker_uuid[:12]}"))) - else: - steps.append(("Worker service", StepResult(False, "Failed to create"))) + if resp.status_code == 201: + app_uuid = resp.json().get("uuid") + print(f" Application created: {app_uuid[:12]}") + steps.append(("Create application", StepResult(True, f"Created: {app_uuid[:12]}"))) + else: + steps.append(("Create application", StepResult(False, f"{resp.status_code}: {resp.text[:200]}"))) + print_summary(steps) + return 1 + except Exception as e: + steps.append(("Create application", StepResult(False, str(e)))) 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)...") + # Step 2: PATCH to dockercompose build_pack (Coolify reads docker-compose.yaml from Git) + print("\n[2/4] Configuring docker-compose build pack...") try: - result = client.deploy_application(api_uuid) - deploy_uuid = _extract_deploy_uuid(result) - if deploy_uuid: - print(f" Deploy queued: {deploy_uuid[:12]}") - r = _wait_deployment(client, deploy_uuid, timeout=300) + resp = httpx.patch( + f"{client.base_url}/api/v1/applications/{app_uuid}", + headers=client.headers, + json={"build_pack": "dockercompose"}, + timeout=30, + ) + if resp.status_code == 200: + print(" Build pack set to dockercompose") + steps.append(("Configure compose", StepResult(True, "dockercompose build pack set"))) else: - r = StepResult(True, "Deploy triggered (no UUID)") + steps.append(("Configure compose", StepResult(False, f"{resp.status_code}: {resp.text[:200]}"))) + print_summary(steps) + return 1 + except Exception as e: + steps.append(("Configure compose", StepResult(False, str(e)))) + print_summary(steps) + return 1 + _print_result(steps[-1][1]) + + # Step 3: Set environment variables (domain, admin credentials, secrets) + print("\n[3/4] Setting environment variables...") + envs = [ + {"key": "POSTGRES_USER", "value": os.environ.get("DB_USER", "crm_user")}, + {"key": "POSTGRES_DB", "value": DB_NAME}, + {"key": "DB_PASSWORD", "value": DB_PASSWORD}, + {"key": "REDIS_PASSWORD", "value": REDIS_PASSWORD}, + {"key": "SECRET_KEY", "value": SECRET_KEY}, + {"key": "ENVIRONMENT", "value": os.environ.get("ENVIRONMENT", "production")}, + {"key": "LOG_LEVEL", "value": os.environ.get("LOG_LEVEL", "INFO")}, + {"key": "SESSION_COOKIE_SECURE", "value": "true"}, + {"key": "STORAGE_PATH", "value": os.environ.get("STORAGE_PATH", "/data/storage")}, + {"key": "CORS_ORIGINS", "value": APP_DOMAIN}, + {"key": "FRONTEND_URL", "value": APP_DOMAIN}, + {"key": "ADMIN_EMAIL", "value": os.environ.get("ADMIN_EMAIL", "admin@media-on.de")}, + {"key": "ADMIN_PASSWORD", "value": os.environ.get("ADMIN_PASSWORD", "Admin123!")}, + ] + try: + resp = httpx.patch( + f"{client.base_url}/api/v1/applications/{app_uuid}/envs/bulk", + headers=client.headers, + json={"data": envs}, + timeout=30, + ) + if resp.status_code in (200, 201): + print(f" {len(envs)} environment variables set") + steps.append(("Set envs", StepResult(True, f"{len(envs)} envs set"))) + else: + steps.append(("Set envs", StepResult(False, f"{resp.status_code}: {resp.text[:200]}"))) + except Exception as e: + steps.append(("Set envs", StepResult(False, str(e)))) + _print_result(steps[-1][1]) + + # Step 4: Deploy via /api/v1/deploy (works for dockercompose build_pack) + print("\n[4/4] Deploying docker-compose stack...") + try: + resp = httpx.post( + f"{client.base_url}/api/v1/deploy", + headers=client.headers, + json={"uuid": app_uuid}, + timeout=30, + ) + if resp.status_code == 200: + deploy_data = resp.json() + deployments = deploy_data.get("deployments", []) + if deployments: + deploy_uuid = deployments[0].get("deployment_uuid", "") + print(f" Deploy queued: {deploy_uuid[:12]}") + r = _wait_deployment(client, deploy_uuid, timeout=600) + else: + r = StepResult(True, "Deploy triggered (no UUID)") + else: + r = StepResult(False, f"{resp.status_code}: {resp.text[:200]}") except Exception as e: r = StepResult(False, f"Deploy failed: {e}") - steps.append(("API deploy", r)) + steps.append(("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_code, tag_output = ssh_run( - f'docker images --format "{{{{.Repository}}}}:{{{{.Tag}}}}" | ' - f'grep "^{api_uuid}:" | grep -v latest | head -1 | ' - f'xargs -I{{}} docker tag {{}} {api_uuid}:latest' - ) - 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, worker_uuid=worker_uuid) + # Verification + print("\n[5/5] Running verification...") + verify_results = run_verification(client, worker_uuid=None) steps.extend(verify_results) all_ok = print_summary(steps)