Files
video-flow/deploy.py
Tony Zhang 33a165a615 feat: video-flow initial commit
- 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
2025-12-12 19:18:27 +08:00

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]()