- app.py: Streamlit UI for video generation workflow - main_flow.py: CLI tool with argparse support - modules/: Business logic modules (script_gen, image_gen, video_gen, composer, etc.) - config.py: Configuration with API keys and paths - requirements.txt: Python dependencies - docs/: System prompt documentation
256 lines
7.1 KiB
Python
256 lines
7.1 KiB
Python
"""
|
|
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/<policy domain="path" rights="none" pattern="@\\*"/<policy domain="path" rights="read|write" pattern="@*"/g' /etc/ImageMagick-6/policy.xml 2>/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]()
|
|
|
|
|
|
|