#!/usr/bin/env python3 """ Easypanel Deploy - Automated deployment via API Authenticates with email/password, gets session token, then deploys services following the exact workflow. Usage: python3 deploy.py --project my-project --service my-service --git-url https://... """ import os import sys import json import argparse import requests from pathlib import Path from urllib.parse import quote def load_env(): """Load environment from .env file.""" env_path = Path(__file__).parent / ".env" if env_path.exists(): for line in env_path.read_text().splitlines(): line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) os.environ.setdefault(k.strip(), v.strip().strip("\"'")) load_env() EASYPANEL_URL = os.environ.get("EASYPANEL_URL", "https://panelwebsite.moreminimore.com") EASYPANEL_USERNAME = os.environ.get("EASYPANEL_USERNAME") EASYPANEL_PASSWORD = os.environ.get("EASYPANEL_PASSWORD") EASYPANEL_DEFAULT_PROJECT = os.environ.get("EASYPANEL_DEFAULT_PROJECT", "default") def get_session_token(email, password): """Authenticate with email/password and get session token.""" if not email or not password: print("Error: EASYPANEL_USERNAME and EASYPANEL_PASSWORD required", file=sys.stderr) sys.exit(1) login_url = f"{EASYPANEL_URL}/api/trpc/auth.login" data = {"json": {"email": email, "password": password, "rememberMe": False}} try: response = requests.post(login_url, json=data) if response.status_code == 200: result = response.json() if "result" in result and "data" in result["result"]: session_data = result["result"]["data"] token = session_data.get("sessionToken") or session_data.get("token") if token: return token session_token = response.cookies.get("sessionToken") if session_token: return session_token print(f"Error: Login failed ({response.status_code})", file=sys.stderr) sys.exit(1) except Exception as e: print(f"Error: {e}", file=sys.stderr) sys.exit(1) def make_request(endpoint, method="GET", data=None, token=None): """Make tRPC-style API request to Easypanel.""" url = f"{EASYPANEL_URL}/api/trpc/{endpoint}" headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} try: if method == "GET": response = requests.get(url, headers=headers) elif method == "POST": response = requests.post(url, headers=headers, json=data) if response.status_code == 401: print(f"Error: Authentication failed (401)", file=sys.stderr) return None response.raise_for_status() result = response.json() if "result" in result: return result["result"].get("data") return result except requests.exceptions.RequestException as e: print(f"Error: {e}", file=sys.stderr) return None def create_service(project_name, service_name, token): print(f"🚀 Creating service: {service_name}") data = {"json": {"projectName": project_name, "serviceName": service_name, "build": {"type": "nixpacks"}}} result = make_request("services.app.createService", "POST", data, token) if result: print(f"✅ Service created: {service_name}") return True print(f"❌ Failed to create service") return False def update_git_source(project_name, service_name, git_url, branch="main", token=None): """Connect Git repository to service.""" print(f"🔗 Connecting Git repository...") data = {"json": {"projectName": project_name, "serviceName": service_name, "repo": git_url, "ref": branch, "path": "/"}} result = make_request("services.app.updateSourceGit", "POST", data, token) if result: print(f"✅ Git repository connected: {git_url}") return True print(f"❌ Failed to connect Git repository") return False def update_build_type(project_name, service_name, token, build_type="nixpacks"): print(f"🔨 Setting build type to {build_type}...") data = {"json": {"projectName": project_name, "serviceName": service_name, "build": {"type": build_type}}} result = make_request("services.app.updateBuild", "POST", data, token) if result: print(f"✅ Build type set: {build_type}") return True print(f"⚠️ Could not update build type (may already be set)") return True def deploy_service(project_name, service_name, token): """Trigger deployment.""" print(f"🎬 Triggering deployment...") data = {"json": {"projectName": project_name, "serviceName": service_name, "forceRebuild": False}} result = make_request("services.app.deployService", "POST", data, token) if result: print(f"✅ Deployment triggered") return True print(f"❌ Failed to trigger deployment") return False def check_status(project_name, service_name, token): """Check deployment status.""" print(f"📊 Checking status...") input_json = json.dumps({"json": {"projectName": project_name, "serviceName": service_name}}) encoded_input = quote(input_json) result = make_request(f"services.app.inspectService?input={encoded_input}", "GET", None, token) if result: status = result.get("status", "unknown") print(f"📊 Status: {status}") if "url" in result: print(f"🌐 URL: {result['url']}") return status print(f"⚠️ Could not retrieve status") return "unknown" def main(): parser = argparse.ArgumentParser(description="Deploy to Easypanel") parser.add_argument("--project", required=True, help="Project name") parser.add_argument("--service", required=True, help="Service name") parser.add_argument("--git-url", required=True, help="Git repository URL") parser.add_argument("--branch", default="main", help="Git branch (default: main)") parser.add_argument("--port", type=int, default=80, help="Port (default: 80)") args = parser.parse_args() print("🚀 Easypanel Deploy") print("=" * 50) print(f"Project: {args.project}") print(f"Service: {args.service}") print(f"Git URL: {args.git_url}") print("=" * 50) print() print("🔐 Authenticating...") token = get_session_token(EASYPANEL_USERNAME, EASYPANEL_PASSWORD) if not token: print("❌ Authentication failed", file=sys.stderr) sys.exit(1) print("✅ Authenticated") print() if not create_service(args.project, args.service, token): print("⚠️ Service may already exist, continuing...") print() if not update_git_source(args.project, args.service, args.git_url, args.branch, token): sys.exit(1) print() if not update_build_type(args.project, args.service, token): sys.exit(1) print() if not deploy_service(args.project, args.service, token): sys.exit(1) print() print("⏳ Waiting for deployment to start...") import time time.sleep(5) status = check_status(args.project, args.service, token) print() print("=" * 50) if status in ["running", "ready", "building", "success"]: print("✅ Deployment successful!") print(f"Service: {args.service}") print(f"Project: {args.project}") print(f"Status: {status}") elif status == "failed": print("❌ Deployment failed!") print("Check logs in Easypanel dashboard") sys.exit(1) else: print("⚠️ Deployment status unknown") print("Check Easypanel dashboard for details") print("=" * 50) if __name__ == "__main__": main()