""" Gloda Video Factory - Deployment Script One-click deployment to remote server using Fabric. """ import os import sys from pathlib import Path from dotenv import load_dotenv from fabric import Connection, Config from invoke import task # Load environment variables load_dotenv() # Server configuration SERVER_IP = os.getenv("SERVER_IP", "") SERVER_USER = os.getenv("SERVER_USER", "root") SERVER_PASS = os.getenv("SERVER_PASS", "") # Remote paths REMOTE_APP_DIR = "/opt/gloda-factory" REMOTE_VENV = f"{REMOTE_APP_DIR}/venv" # Files to upload LOCAL_FILES = [ "config.py", "web_app.py", "requirements.txt", ".env", "modules/__init__.py", "modules/utils.py", "modules/brain.py", "modules/factory.py", "modules/editor.py", ] def get_connection() -> Connection: """Create SSH connection to remote server.""" if not SERVER_IP or not SERVER_PASS: raise ValueError("SERVER_IP and SERVER_PASS must be set in .env") config = Config(overrides={"sudo": {"password": SERVER_PASS}}) return Connection( host=SERVER_IP, user=SERVER_USER, connect_kwargs={"password": SERVER_PASS}, config=config ) def deploy(): """Full deployment: setup server, upload code, start app.""" print("šŸš€ Starting deployment...") conn = get_connection() # Step 1: Install system dependencies print("\nšŸ“¦ Step 1/5: Installing system dependencies...") install_dependencies(conn) # Step 2: Create app directory print("\nšŸ“ Step 2/5: Setting up directories...") setup_directories(conn) # Step 3: Upload code print("\nšŸ“¤ Step 3/5: Uploading code...") upload_code(conn) # Step 4: Setup Python environment print("\nšŸ Step 4/5: Setting up Python environment...") setup_python(conn) # Step 5: Start application print("\nšŸŽ¬ Step 5/5: Starting application...") start_app(conn) print(f"\nāœ… Deployment complete!") print(f"🌐 Access the app at: http://{SERVER_IP}:8501") conn.close() def install_dependencies(conn: Connection): """Install system-level dependencies.""" commands = [ "apt-get update -qq", "apt-get install -y -qq python3 python3-pip python3-venv", "apt-get install -y -qq ffmpeg imagemagick", "apt-get install -y -qq fonts-liberation fonts-dejavu-core", ] for cmd in commands: print(f" Running: {cmd[:50]}...") conn.sudo(cmd, hide=True) # Configure ImageMagick policy (allow PDF/SVG for text rendering) policy_fix = """ sed -i 's//dev/null || true """ conn.sudo(policy_fix, hide=True, warn=True) print(" āœ… System dependencies installed") def setup_directories(conn: Connection): """Create application directories on remote server.""" conn.sudo(f"mkdir -p {REMOTE_APP_DIR}/modules", hide=True) conn.sudo(f"mkdir -p {REMOTE_APP_DIR}/output", hide=True) conn.sudo(f"mkdir -p {REMOTE_APP_DIR}/assets/fonts", hide=True) conn.sudo(f"chown -R {SERVER_USER}:{SERVER_USER} {REMOTE_APP_DIR}", hide=True) print(f" āœ… Directories created at {REMOTE_APP_DIR}") def upload_code(conn: Connection): """Upload application code to remote server.""" local_base = Path(__file__).parent for file_path in LOCAL_FILES: local_file = local_base / file_path remote_file = f"{REMOTE_APP_DIR}/{file_path}" if local_file.exists(): # Ensure remote directory exists remote_dir = str(Path(remote_file).parent) conn.run(f"mkdir -p {remote_dir}", hide=True) # Upload file conn.put(str(local_file), remote_file) print(f" āœ… Uploaded: {file_path}") else: print(f" āš ļø Skipped (not found): {file_path}") print(" āœ… Code uploaded") def setup_python(conn: Connection): """Setup Python virtual environment and install dependencies.""" with conn.cd(REMOTE_APP_DIR): # Create virtual environment conn.run(f"python3 -m venv {REMOTE_VENV}", hide=True) # Upgrade pip conn.run(f"{REMOTE_VENV}/bin/pip install --upgrade pip -q", hide=True) # Install requirements conn.run(f"{REMOTE_VENV}/bin/pip install -r requirements.txt -q", hide=True) print(" āœ… Python environment ready") def start_app(conn: Connection): """Start the Streamlit application.""" # Stop existing process if any conn.run("pkill -f 'streamlit run web_app.py' || true", hide=True, warn=True) # Start in background with nohup start_cmd = f""" cd {REMOTE_APP_DIR} && \ nohup {REMOTE_VENV}/bin/streamlit run web_app.py \ --server.port 8501 \ --server.address 0.0.0.0 \ --server.headless true \ --browser.gatherUsageStats false \ > /var/log/gloda-factory.log 2>&1 & """ conn.run(start_cmd, hide=True) # Wait and verify import time time.sleep(3) result = conn.run("pgrep -f 'streamlit run web_app.py'", hide=True, warn=True) if result.ok: print(f" āœ… Application started (PID: {result.stdout.strip()})") else: print(" āš ļø Application may not have started. Check logs.") def stop_app(): """Stop the running application.""" print("šŸ›‘ Stopping application...") conn = get_connection() conn.run("pkill -f 'streamlit run web_app.py' || true", hide=True, warn=True) conn.close() print("āœ… Application stopped") def logs(): """Show application logs.""" print("šŸ“‹ Recent logs:") conn = get_connection() conn.run("tail -50 /var/log/gloda-factory.log", warn=True) conn.close() def status(): """Check application status.""" print("šŸ“Š Checking status...") conn = get_connection() result = conn.run("pgrep -f 'streamlit run web_app.py'", hide=True, warn=True) if result.ok: print(f"āœ… Application is running (PID: {result.stdout.strip()})") print(f"🌐 URL: http://{SERVER_IP}:8501") else: print("āŒ Application is not running") conn.close() def restart(): """Restart the application.""" print("šŸ”„ Restarting application...") conn = get_connection() # Stop conn.run("pkill -f 'streamlit run web_app.py' || true", hide=True, warn=True) import time time.sleep(2) # Start start_app(conn) conn.close() if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Gloda Factory Deployment Tool") parser.add_argument( "command", choices=["deploy", "start", "stop", "restart", "status", "logs"], help="Deployment command to run" ) args = parser.parse_args() commands = { "deploy": deploy, "stop": stop_app, "status": status, "logs": logs, "restart": restart, "start": lambda: start_app(get_connection()), } commands[args.command]()