better deploy choice

This commit is contained in:
Kohaku-Blueleaf
2025-10-27 12:27:20 +08:00
parent 8fef619f6f
commit 6f84e09f66
2 changed files with 229 additions and 3 deletions

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Deploy KohakuBoard integrated with KohakuHub (shared database for SSO)
OPTIONAL: This is only if you want to share database with KohakuHub.
For fully standalone deployment, use deploy_board.py instead.
"""
import subprocess
import sys
def main():
print("=" * 60)
print("KohakuBoard Integrated Deployment (OPTIONAL)")
print("Shares database with KohakuHub for unified accounts")
print("=" * 60)
# Check if KohakuHub is running (optional warning, not error)
print("\n[0/3] Checking KohakuHub services...")
result = subprocess.run(
["docker", "ps", "--filter", "name=postgres", "--format", "{{.Names}}"],
capture_output=True,
text=True,
)
if "postgres" not in result.stdout:
print("⚠️ Warning: KohakuHub PostgreSQL is not running")
print("\nThis deployment expects to connect to KohakuHub's database.")
print("If you want fully standalone deployment, use:")
print(" python scripts/deploy_board.py")
print("\nIf you want to deploy KohakuHub first:")
print(" 1. cp docker-compose.example.yml docker-compose.yml")
print(" 2. Edit docker-compose.yml (change passwords and secrets)")
print(" 3. ./deploy.sh")
print("\nContinuing anyway (will fail if database is not accessible)...")
else:
print("✓ KohakuHub PostgreSQL is running")
# Build frontend
print("\n[1/3] Building frontend...")
result = subprocess.run(
["npm", "install", "--prefix", "./src/kohaku-board-ui"],
check=False,
)
if result.returncode != 0:
print("✗ Failed to install dependencies")
sys.exit(1)
result = subprocess.run(
["npm", "run", "build", "--prefix", "./src/kohaku-board-ui"],
check=False,
)
if result.returncode != 0:
print("✗ Failed to build frontend")
sys.exit(1)
print("\n[2/3] Starting KohakuBoard services...")
result = subprocess.run(
[
"docker-compose",
"-f",
"docker-compose.board-integrated.yml",
"up",
"-d",
"--build",
],
check=False,
)
if result.returncode != 0:
print("✗ Failed to start Docker services")
sys.exit(1)
print("\n" + "=" * 60)
print("✓ KohakuBoard deployed successfully (integrated mode)!")
print("=" * 60)
print(f"\nKohakuHub: http://localhost:28080")
print(f"KohakuBoard: http://localhost:28081")
print(f"\n⚡ SSO enabled: Login works across both systems")
print(f"\n📊 Shared database: Users are unified")
print("\nView logs: docker-compose -f docker-compose.board-integrated.yml logs -f")
print("Stop services: docker-compose -f docker-compose.board-integrated.yml down")
print()
if __name__ == "__main__":
main()

View File

@@ -34,8 +34,9 @@ def cli():
@click.argument("folder", type=click.Path(exists=True))
@click.option("--port", default=48889, help="Server port (default: 48889)")
@click.option("--host", default="0.0.0.0", help="Server host (default: 0.0.0.0)")
@click.option("--reload", is_flag=True, help="Enable auto-reload for development")
@click.option("--no-browser", is_flag=True, help="Do not open browser automatically")
def open(folder, port, host, no_browser):
def open(folder, port, host, reload, no_browser):
"""Open local board folder in browser
Starts a local web server to browse boards in the specified folder.
@@ -44,7 +45,7 @@ def open(folder, port, host, no_browser):
Examples:
kobo open ./kohakuboard
kobo open /path/to/experiments --port 8080
kobo open ./boards --no-browser
kobo open ./boards --reload --no-browser
"""
folder_path = Path(folder).resolve()
@@ -57,6 +58,8 @@ def open(folder, port, host, no_browser):
click.echo("🚀 Starting KohakuBoard server (local mode)")
click.echo(f"📁 Board directory: {folder_path}")
click.echo(f"🌐 Server URL: http://localhost:{port}")
if reload:
click.echo(f"🔄 Auto-reload: Enabled")
click.echo()
# Open browser after delay
@@ -79,7 +82,7 @@ def open(folder, port, host, no_browser):
"kohakuboard.main:app",
host=host,
port=port,
reload=False,
reload=reload,
log_level="info",
)
except KeyboardInterrupt:
@@ -87,6 +90,144 @@ def open(folder, port, host, no_browser):
sys.exit(0)
@cli.command()
@click.option("--host", default="0.0.0.0", help="Server host (default: 0.0.0.0)")
@click.option("--port", default=48889, help="Server port (default: 48889)")
@click.option(
"--data-dir",
default="./kohakuboard",
help="Board data directory (default: ./kohakuboard)",
)
@click.option(
"--db",
default="sqlite:///kohakuboard.db",
help="Database URL (default: sqlite:///kohakuboard.db)",
)
@click.option(
"--db-backend",
type=click.Choice(["sqlite", "postgres"]),
default="sqlite",
help="Database backend (default: sqlite)",
)
@click.option(
"--reload",
is_flag=True,
help="Enable auto-reload for development",
)
@click.option(
"--workers",
default=1,
help="Number of worker processes (default: 1, use 4+ for production)",
)
@click.option(
"--session-secret",
help="Session secret for authentication (required in production)",
)
@click.option(
"--no-browser",
is_flag=True,
help="Do not open browser automatically",
)
def serve(
host, port, data_dir, db, db_backend, reload, workers, session_secret, no_browser
):
"""Start KohakuBoard server in remote mode with authentication
Lightweight entry point for both testing and production deployments.
No Docker required - just Python and a database.
Examples:
# Development with auto-reload (SQLite)
kobo serve --reload
# Production with PostgreSQL
kobo serve --db postgresql://user:pass@localhost/kohakuboard \\
--db-backend postgres \\
--workers 4 \\
--session-secret $(openssl rand -hex 32)
# Custom configuration
kobo serve --port 8080 \\
--data-dir /var/kohakuboard \\
--db sqlite:///data/board.db \\
--workers 2
"""
data_dir_path = Path(data_dir).resolve()
data_dir_path.mkdir(parents=True, exist_ok=True)
# Set environment for remote mode
os.environ["KOHAKU_BOARD_MODE"] = "remote"
os.environ["KOHAKU_BOARD_BOARD_DATA_DIR"] = str(data_dir_path)
os.environ["KOHAKU_BOARD_PORT"] = str(port)
os.environ["KOHAKU_BOARD_HOST"] = host
os.environ["KOHAKU_BOARD_DB_BACKEND"] = db_backend
os.environ["KOHAKU_BOARD_DATABASE_URL"] = db
os.environ["KOHAKU_BOARD_BASE_URL"] = f"http://localhost:{port}"
# Session secret (required for production)
if session_secret:
os.environ["KOHAKU_BOARD_AUTH_SESSION_SECRET"] = session_secret
elif workers > 1 and not reload:
click.echo(
"⚠️ Warning: Using default session secret in multi-worker mode", err=True
)
click.echo(" Generate one with: openssl rand -hex 32", err=True)
click.echo()
# Auth config defaults (can be overridden with env vars)
os.environ.setdefault("KOHAKU_BOARD_AUTH_REQUIRE_EMAIL_VERIFICATION", "false")
os.environ.setdefault("KOHAKU_BOARD_AUTH_INVITATION_ONLY", "false")
click.echo("🚀 Starting KohakuBoard server (remote mode)")
click.echo(f"📁 Data directory: {data_dir_path}")
click.echo(f"💾 Database: {db}")
click.echo(f"🌐 Server URL: http://localhost:{port}")
click.echo(f"👥 Authentication: Enabled")
if reload:
click.echo(f"🔄 Auto-reload: Enabled (development)")
if workers > 1:
click.echo(f"⚡ Workers: {workers}")
click.echo()
# Open browser after delay
if not no_browser:
def open_browser():
time.sleep(2) # Wait for server to start
click.echo(f"🔗 Opening browser at http://localhost:{port}")
try:
webbrowser.open(f"http://localhost:{port}")
except Exception as e:
click.echo(f"⚠️ Could not open browser: {e}", err=True)
thread = threading.Thread(target=open_browser, daemon=True)
thread.start()
# Run uvicorn
try:
if reload or workers == 1:
# Single worker with optional reload
uvicorn.run(
"kohakuboard.main:app",
host=host,
port=port,
reload=reload,
log_level="info",
)
else:
# Multi-worker production mode
uvicorn.run(
"kohakuboard.main:app",
host=host,
port=port,
workers=workers,
log_level="info",
)
except KeyboardInterrupt:
click.echo("\n👋 Server stopped")
sys.exit(0)
@cli.command()
@click.argument("folder", type=click.Path(exists=True))
@click.option(