#!/bin/bash # # ServerKit CLI - Management tool for ServerKit # # Architecture: # - Backend: systemd service (runs directly on host) # - Frontend: Docker container (nginx) # # Usage: serverkit [options] # set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Configuration INSTALL_DIR="${SERVERKIT_DIR:-/opt/serverkit}" VENV_DIR="$INSTALL_DIR/venv" BACKEND_SERVICE="serverkit" FRONTEND_CONTAINER="serverkit-frontend" # Helper functions print_header() { echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE} ServerKit CLI${NC}" echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } print_success() { echo -e "${GREEN}✓ $1${NC}"; } print_error() { echo -e "${RED}✗ $1${NC}"; } print_warning() { echo -e "${YELLOW}! $1${NC}"; } print_info() { echo -e "${BLUE}→ $1${NC}"; } check_root() { if [ "$EUID" -ne 0 ]; then print_error "This command requires root privileges. Run with sudo." exit 1 fi } check_installed() { if [ ! -d "$INSTALL_DIR" ]; then print_error "ServerKit is not installed in $INSTALL_DIR" print_info "Run the install script first" exit 1 fi } run_cli() { # Run CLI commands directly on host using the virtual environment cd "$INSTALL_DIR/backend" source "$VENV_DIR/bin/activate" python cli.py "$@" } # Version management VERSION_FILE="$INSTALL_DIR/VERSION" REMOTE_VERSION_URL="https://raw.githubusercontent.com/jhd3197/serverkit/main/VERSION" get_local_version() { if [ -f "$VERSION_FILE" ]; then cat "$VERSION_FILE" | tr -d '\n\r ' else echo "unknown" fi } get_remote_version() { curl -sf "$REMOTE_VERSION_URL" 2>/dev/null | tr -d '\n\r ' || echo "unknown" } compare_versions() { # Returns: 0 if equal, 1 if v1 > v2, 2 if v1 < v2 local v1="$1" local v2="$2" if [ "$v1" = "$v2" ]; then return 0 fi # Compare version components local v1_major=$(echo "$v1" | cut -d. -f1) local v1_minor=$(echo "$v1" | cut -d. -f2) local v1_patch=$(echo "$v1" | cut -d. -f3) local v2_major=$(echo "$v2" | cut -d. -f1) local v2_minor=$(echo "$v2" | cut -d. -f2) local v2_patch=$(echo "$v2" | cut -d. -f3) if [ "$v1_major" -gt "$v2_major" ] 2>/dev/null; then return 1; fi if [ "$v1_major" -lt "$v2_major" ] 2>/dev/null; then return 2; fi if [ "$v1_minor" -gt "$v2_minor" ] 2>/dev/null; then return 1; fi if [ "$v1_minor" -lt "$v2_minor" ] 2>/dev/null; then return 2; fi if [ "$v1_patch" -gt "$v2_patch" ] 2>/dev/null; then return 1; fi if [ "$v1_patch" -lt "$v2_patch" ] 2>/dev/null; then return 2; fi return 0 } cmd_version() { local version=$(get_local_version) echo "ServerKit version $version" } cmd_check_update() { print_info "Checking for updates..." local local_version=$(get_local_version) local remote_version=$(get_remote_version) echo "" echo " Installed version: $local_version" echo " Latest version: $remote_version" echo "" if [ "$remote_version" = "unknown" ]; then print_warning "Could not check remote version (network error?)" return 1 fi set +e # Temporarily disable exit on error compare_versions "$local_version" "$remote_version" local result=$? set -e # Re-enable exit on error if [ $result -eq 2 ]; then print_success "Update available! Run 'sudo serverkit update' to upgrade." return 0 elif [ $result -eq 0 ]; then print_success "You are running the latest version." return 0 else print_info "You are running a newer version than released." return 0 fi } # Commands cmd_help() { print_header echo "" echo "Usage: serverkit [options]" echo "" echo "Service Commands:" echo " start Start all services" echo " stop Stop all services" echo " restart Restart all services" echo " status Show service status" echo " logs [service] View logs (backend, frontend, or all)" echo " update [options] Update ServerKit" echo " --branch Switch to specific branch (e.g., dev)" echo " --force Skip version check" echo " branch Show current branch" echo " uninstall Uninstall ServerKit" echo "" echo "User Management:" echo " create-admin Create a new admin user" echo " reset-password Reset a user's password" echo " unlock-user Unlock a locked user account" echo " list-users List all users" echo " make-admin Promote user to admin" echo " deactivate-user Deactivate a user account" echo " activate-user Activate a user account" echo "" echo "Database Commands:" echo " init-db Initialize the database" echo " migrate-db Apply database migrations" echo " backup-db Backup the database" echo " restore-db Restore database from backup" echo "" echo "Cleanup Commands:" echo " cleanup-apps Delete all apps and containers" echo " factory-reset Complete reset (delete everything)" echo "" echo "App Management:" echo " create-app Show how to create apps (via web UI)" echo " list-apps [--all] List deployed apps (--all shows Docker containers)" echo " start-app Start an app" echo " stop-app Stop an app" echo " app-logs View app logs" echo "" echo "Site Management:" echo " setup-proxy Setup host nginx as reverse proxy (run once)" echo " add-site Add a new site/domain" echo " list-sites List all configured sites" echo " remove-site Remove a site" echo "" echo "Version Commands:" echo " version Show current version" echo " check-update Check if updates are available" echo "" echo "Utility Commands:" echo " generate-keys Generate secure keys for .env" echo " config Edit configuration" echo "" echo "Examples:" echo " sudo serverkit start" echo " sudo serverkit create-admin" echo " sudo serverkit add-site test.mysite.com 3000" echo " serverkit logs backend" echo "" } cmd_start() { check_root check_installed print_info "Starting ServerKit..." # Start backend (systemd) systemctl start $BACKEND_SERVICE # Start frontend (Docker) cd "$INSTALL_DIR" docker compose up -d sleep 3 print_success "ServerKit started" cmd_status_brief } cmd_stop() { check_root check_installed print_info "Stopping ServerKit..." # Stop backend (systemd) systemctl stop $BACKEND_SERVICE || true # Stop frontend (Docker) cd "$INSTALL_DIR" docker compose down || true print_success "ServerKit stopped" } cmd_restart() { check_root check_installed print_info "Restarting ServerKit..." # Restart backend (systemd) systemctl restart $BACKEND_SERVICE # Restart frontend (Docker) cd "$INSTALL_DIR" docker compose restart sleep 3 print_success "ServerKit restarted" cmd_status_brief } cmd_status_brief() { # Quick status check echo "" # Backend status if systemctl is-active --quiet $BACKEND_SERVICE; then print_success "Backend: running" else print_error "Backend: stopped" fi # Frontend status if docker ps --format '{{.Names}}' | grep -q $FRONTEND_CONTAINER; then print_success "Frontend: running" else print_error "Frontend: stopped" fi # API health if curl -s http://127.0.0.1:5000/api/v1/system/health > /dev/null 2>&1; then print_success "API: healthy" else print_warning "API: not responding" fi } cmd_status() { check_installed print_header echo "" echo "Backend Service (systemd):" echo "─────────────────────────────" systemctl status $BACKEND_SERVICE --no-pager -l 2>/dev/null || print_warning "Backend service not found" echo "" echo "Frontend Container (Docker):" echo "─────────────────────────────" cd "$INSTALL_DIR" docker compose ps 2>/dev/null || print_warning "Frontend container not found" echo "" cmd_status_brief } cmd_logs() { check_installed service="${1:-all}" case "$service" in backend) journalctl -u $BACKEND_SERVICE -f ;; frontend) cd "$INSTALL_DIR" docker compose logs -f ;; all|*) print_info "Showing backend logs (Ctrl+C to switch to frontend)..." echo "" # Show both in split view isn't easy in bash, so show backend first journalctl -u $BACKEND_SERVICE -n 50 --no-pager echo "" print_info "Showing frontend logs..." echo "" cd "$INSTALL_DIR" docker compose logs --tail=50 echo "" print_info "For live logs, use: serverkit logs backend OR serverkit logs frontend" ;; esac } cmd_branch() { check_installed cd "$INSTALL_DIR" local current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") local current_commit=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") echo "" echo " Current branch: $current_branch" echo " Current commit: $current_commit" echo " Version: $(get_local_version)" echo "" # Show available remote branches print_info "Available branches:" git branch -r 2>/dev/null | grep -v HEAD | sed 's/origin\// /' | head -10 echo "" } cmd_update() { local force_update=false local target_branch="" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --force|-f) force_update=true shift ;; --branch|-b) target_branch="$2" shift 2 ;; *) shift ;; esac done check_root check_installed print_header echo "" cd "$INSTALL_DIR" # Get current branch local current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main") # If no branch specified, use current branch (or main if detached) if [ -z "$target_branch" ]; then target_branch="$current_branch" if [ "$target_branch" = "HEAD" ]; then target_branch="main" fi fi # Get versions CURRENT_VERSION=$(get_local_version) # For non-main branches, skip version comparison if [ "$target_branch" != "main" ]; then force_update=true print_warning "Updating to branch: $target_branch (dev mode)" echo "" else REMOTE_VERSION=$(get_remote_version) echo " Installed version: $CURRENT_VERSION" echo " Latest version: $REMOTE_VERSION" echo "" fi # Check if update is needed (skip if --force or non-main branch) if [ "$force_update" = false ] && [ "$REMOTE_VERSION" != "unknown" ]; then set +e # Temporarily disable exit on error compare_versions "$CURRENT_VERSION" "$REMOTE_VERSION" local result=$? set -e # Re-enable exit on error if [ $result -eq 0 ]; then print_success "Already running the latest version." echo "" read -p "Force update anyway? [y/N] " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 0 fi fi fi print_info "Updating ServerKit to branch: $target_branch..." # Stop services print_info "Stopping services..." systemctl stop $BACKEND_SERVICE || true docker compose down || true # Fetch all branches and pull changes print_info "Fetching latest changes..." git fetch origin --prune # Checkout target branch print_info "Switching to branch: $target_branch..." git checkout "$target_branch" 2>/dev/null || git checkout -b "$target_branch" "origin/$target_branch" git reset --hard "origin/$target_branch" # Restore executable permissions chmod +x "$INSTALL_DIR/serverkit" chmod +x "$INSTALL_DIR/scripts/"*.sh 2>/dev/null || true # Get new version from VERSION file NEW_VERSION=$(get_local_version) NEW_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") # Update Python dependencies print_info "Updating Python dependencies..." source "$VENV_DIR/bin/activate" pip install -r "$INSTALL_DIR/backend/requirements.txt" --quiet # Sync app templates print_info "Syncing app templates..." mkdir -p /etc/serverkit/templates cp -r "$INSTALL_DIR/backend/templates/"*.yaml /etc/serverkit/templates/ 2>/dev/null || true cp -r "$INSTALL_DIR/backend/templates/"*.yml /etc/serverkit/templates/ 2>/dev/null || true # Clean up Docker to prevent networking issues print_info "Cleaning up Docker..." docker network prune -f 2>/dev/null || true docker system prune -f 2>/dev/null || true # Rebuild frontend (no cache to ensure fresh build) print_info "Rebuilding frontend..." docker compose build --no-cache # Reload systemd in case service file changed systemctl daemon-reload # Restart services print_info "Starting services..." systemctl start $BACKEND_SERVICE docker compose up -d sleep 5 # Track successful update curl -s "https://serverkit.ai/track/update?v=${NEW_VERSION}" >/dev/null 2>&1 || true echo "" print_success "ServerKit updated!" echo " Branch: $target_branch" echo " Commit: $NEW_COMMIT" echo " Version: $NEW_VERSION" echo "" if [ "$target_branch" != "main" ]; then print_warning "Running on '$target_branch' branch. Use 'serverkit update --branch main' to return to stable." fi cmd_status_brief } cmd_uninstall() { check_root print_header echo "" print_warning "This will remove ServerKit and all data!" echo "" echo "The following will be removed:" echo " - ServerKit backend service (systemd)" echo " - Frontend Docker container and images" echo " - Installation directory ($INSTALL_DIR)" echo " - Configuration (/etc/serverkit)" echo " - App data (/var/serverkit)" echo " - Logs (/var/log/serverkit)" echo " - Nginx site configs for ServerKit" echo " - CLI symlink (/usr/local/bin/serverkit)" echo "" read -p "Are you sure? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then # Track uninstall before removing files UNINSTALL_VERSION=$(get_local_version) curl -s "https://serverkit.ai/track/uninstall?v=${UNINSTALL_VERSION}" >/dev/null 2>&1 || true print_info "Stopping services..." systemctl stop $BACKEND_SERVICE 2>/dev/null || true systemctl disable $BACKEND_SERVICE 2>/dev/null || true cd "$INSTALL_DIR" 2>/dev/null && docker compose down -v 2>/dev/null || true # Move to safe directory before deleting install dir cd /tmp 2>/dev/null || cd / || true print_info "Removing files..." rm -f /etc/systemd/system/serverkit.service rm -f /usr/local/bin/serverkit rm -rf "$INSTALL_DIR" rm -rf /var/log/serverkit rm -rf /var/lib/serverkit # Remove ServerKit config and app data rm -rf /etc/serverkit rm -rf /var/serverkit # Remove nginx site configs installed by ServerKit print_info "Removing nginx configs..." rm -f /etc/nginx/sites-enabled/serverkit.conf rm -f /etc/nginx/sites-available/serverkit.conf rm -f /etc/nginx/sites-available/example.conf.template # Reload nginx if it's still running (don't stop it — user may use it for other sites) if systemctl is-active --quiet nginx 2>/dev/null; then systemctl reload nginx 2>/dev/null || true fi systemctl daemon-reload print_success "ServerKit uninstalled" echo "" echo "Note: System packages (Docker, Node.js, nginx, Python) were NOT removed." echo "Remove them manually if no longer needed." echo "" echo "To reinstall, run:" echo " cd ~ && curl -fsSL https://serverkit.ai/install.sh | sudo bash" fi } cmd_create_admin() { check_installed print_info "Creating admin user..." run_cli create-admin } cmd_reset_password() { check_installed print_info "Resetting password..." run_cli reset-password } cmd_unlock_user() { check_installed run_cli unlock-user } cmd_list_users() { check_installed run_cli list-users } cmd_make_admin() { check_installed run_cli make-admin } cmd_deactivate_user() { check_installed run_cli deactivate-user } cmd_activate_user() { check_installed run_cli activate-user } cmd_init_db() { check_installed run_cli init-db } cmd_migrate_db() { check_installed run_cli migrate-db } cmd_cleanup_apps() { check_root check_installed DELETE_VOLUMES="" KEEP_DB="" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --delete-volumes|-v) DELETE_VOLUMES="--delete-volumes" shift ;; --keep-db|-k) KEEP_DB="--keep-db" shift ;; *) shift ;; esac done run_cli cleanup-apps $DELETE_VOLUMES $KEEP_DB } cmd_factory_reset() { check_root check_installed run_cli factory-reset } cmd_backup_db() { check_installed BACKUP_DIR="$INSTALL_DIR/backups" BACKUP_FILE="$BACKUP_DIR/serverkit-$(date +%Y%m%d-%H%M%S).db" DB_FILE="$INSTALL_DIR/backend/instance/serverkit.db" mkdir -p "$BACKUP_DIR" if [ -f "$DB_FILE" ]; then cp "$DB_FILE" "$BACKUP_FILE" print_success "Database backed up to $BACKUP_FILE" else print_error "Database file not found: $DB_FILE" exit 1 fi } cmd_restore_db() { check_root check_installed BACKUP_DIR="$INSTALL_DIR/backups" DB_FILE="$INSTALL_DIR/backend/instance/serverkit.db" if [ -z "$1" ]; then echo "Available backups:" ls -la "$BACKUP_DIR"/*.db 2>/dev/null || echo "No backups found" echo "" read -p "Enter backup file path: " BACKUP_FILE else BACKUP_FILE="$1" fi if [ ! -f "$BACKUP_FILE" ]; then print_error "Backup file not found: $BACKUP_FILE" exit 1 fi print_warning "This will overwrite the current database!" read -p "Continue? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then # Stop backend before restore systemctl stop $BACKEND_SERVICE cp "$BACKUP_FILE" "$DB_FILE" # Restart backend systemctl start $BACKEND_SERVICE print_success "Database restored" fi } cmd_generate_keys() { print_header echo "" echo "Add these to your .env file:" echo "" SECRET_KEY=$(openssl rand -hex 32) JWT_SECRET_KEY=$(openssl rand -hex 32) echo "SECRET_KEY=$SECRET_KEY" echo "JWT_SECRET_KEY=$JWT_SECRET_KEY" echo "" } cmd_config() { check_installed ${EDITOR:-nano} "$INSTALL_DIR/.env" print_info "Restart ServerKit to apply changes: sudo serverkit restart" } cmd_setup_proxy() { check_root check_installed print_info "Setting up host nginx as reverse proxy..." if [ -f "$INSTALL_DIR/scripts/setup-nginx-proxy.sh" ]; then chmod +x "$INSTALL_DIR/scripts/setup-nginx-proxy.sh" "$INSTALL_DIR/scripts/setup-nginx-proxy.sh" else print_error "Setup script not found. Run 'serverkit update' first." exit 1 fi } cmd_create_app() { print_header echo "" echo "To create a new app, use the ServerKit web interface:" echo "" echo " 1. Open ServerKit in your browser" echo " 2. Go to Templates in the sidebar" echo " 3. Choose from 60+ available templates:" echo " - PHP Application (custom PHP projects)" echo " - Python Application (Flask, FastAPI, Django)" echo " - Node.js Application (Express, Next.js)" echo " - WordPress, Ghost, Nextcloud, and many more" echo " 4. Click Install and configure your app" echo "" echo "The web interface provides:" echo " ✓ One-click installation" echo " ✓ Automatic port assignment" echo " ✓ Configuration UI" echo " ✓ Update management" echo "" } cmd_list_apps() { check_installed # Pass --all flag if provided if [ "$1" = "--all" ] || [ "$1" = "-a" ]; then run_cli list-apps --all else run_cli list-apps fi } cmd_start_app() { check_root NAME="$1" if [ -z "$NAME" ]; then echo "Usage: serverkit start-app " cmd_list_apps exit 1 fi APP_DIR="/var/www/$NAME" if [ ! -d "$APP_DIR" ]; then print_error "App not found: $NAME" exit 1 fi print_info "Starting $NAME..." cd "$APP_DIR" docker compose up -d print_success "$NAME started" } cmd_stop_app() { check_root NAME="$1" if [ -z "$NAME" ]; then echo "Usage: serverkit stop-app " cmd_list_apps exit 1 fi APP_DIR="/var/www/$NAME" if [ ! -d "$APP_DIR" ]; then print_error "App not found: $NAME" exit 1 fi print_info "Stopping $NAME..." cd "$APP_DIR" docker compose down print_success "$NAME stopped" } cmd_app_logs() { NAME="$1" if [ -z "$NAME" ]; then echo "Usage: serverkit app-logs " cmd_list_apps exit 1 fi APP_DIR="/var/www/$NAME" if [ ! -d "$APP_DIR" ]; then print_error "App not found: $NAME" exit 1 fi cd "$APP_DIR" docker compose logs -f } cmd_add_site() { check_root DOMAIN="$1" PORT="${2:-3000}" if [ -z "$DOMAIN" ]; then echo "Usage: serverkit add-site [port]" echo "" echo "Examples:" echo " serverkit add-site test.builditdesign.com" echo " serverkit add-site myapp.builditdesign.com 3000" exit 1 fi SITE_FILE="/etc/nginx/sites-available/${DOMAIN}.conf" if [ -f "$SITE_FILE" ]; then print_warning "Site config already exists: $SITE_FILE" read -p "Overwrite? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 0 fi fi print_info "Creating site config for $DOMAIN..." cat > "$SITE_FILE" << EOF # Site: $DOMAIN # Created: $(date) server { listen 80; server_name $DOMAIN; location / { proxy_pass http://127.0.0.1:$PORT; proxy_http_version 1.1; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; } } EOF # Enable the site ln -sf "$SITE_FILE" /etc/nginx/sites-enabled/ # Test and reload nginx if nginx -t 2>/dev/null; then systemctl reload nginx print_success "Site added: $DOMAIN → localhost:$PORT" echo "" echo "Next steps:" echo " 1. Add DNS record in Cloudflare:" echo " Type: A, Name: ${DOMAIN%%.*}, Content: $(curl -s ifconfig.me)" echo " 2. Start your app on port $PORT" echo "" else print_error "Nginx config error. Check: $SITE_FILE" rm -f "/etc/nginx/sites-enabled/${DOMAIN}.conf" exit 1 fi } cmd_list_sites() { print_header echo "" echo "Configured Sites:" echo "─────────────────────────────────────────" if [ -d /etc/nginx/sites-enabled ]; then for site in /etc/nginx/sites-enabled/*.conf; do if [ -f "$site" ]; then DOMAIN=$(grep -m1 "server_name" "$site" | awk '{print $2}' | tr -d ';') PORT=$(grep -m1 "proxy_pass" "$site" | grep -oP ':\K[0-9]+' || echo "static") ENABLED="✓" printf " %s %-35s → port %s\n" "$ENABLED" "$DOMAIN" "$PORT" fi done else echo " No sites configured. Run 'serverkit setup-proxy' first." fi echo "" } cmd_remove_site() { check_root DOMAIN="$1" if [ -z "$DOMAIN" ]; then echo "Usage: serverkit remove-site " cmd_list_sites exit 1 fi SITE_FILE="/etc/nginx/sites-available/${DOMAIN}.conf" ENABLED_FILE="/etc/nginx/sites-enabled/${DOMAIN}.conf" if [ ! -f "$SITE_FILE" ]; then print_error "Site not found: $DOMAIN" exit 1 fi print_warning "This will remove the site config for $DOMAIN" read -p "Continue? (y/N) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then rm -f "$ENABLED_FILE" rm -f "$SITE_FILE" systemctl reload nginx print_success "Site removed: $DOMAIN" fi } # Main case "${1:-help}" in help|--help|-h) cmd_help ;; start) cmd_start ;; stop) cmd_stop ;; restart) cmd_restart ;; status) cmd_status ;; logs) cmd_logs "$2" ;; update) shift cmd_update "$@" ;; branch) cmd_branch ;; version|--version|-v) cmd_version ;; check-update) cmd_check_update ;; uninstall) cmd_uninstall ;; create-admin) cmd_create_admin ;; reset-password) cmd_reset_password ;; unlock-user) cmd_unlock_user ;; list-users) cmd_list_users ;; make-admin) cmd_make_admin ;; deactivate-user) cmd_deactivate_user ;; activate-user) cmd_activate_user ;; init-db) cmd_init_db ;; migrate-db) cmd_migrate_db ;; cleanup-apps) shift cmd_cleanup_apps "$@" ;; factory-reset) cmd_factory_reset ;; backup-db) cmd_backup_db ;; restore-db) cmd_restore_db "$2" ;; generate-keys) cmd_generate_keys ;; config) cmd_config ;; setup-proxy) cmd_setup_proxy ;; create-app) cmd_create_app "$2" "$3" "$4" ;; list-apps) cmd_list_apps "$2" ;; start-app) cmd_start_app "$2" ;; stop-app) cmd_stop_app "$2" ;; app-logs) cmd_app_logs "$2" ;; add-site) cmd_add_site "$2" "$3" ;; list-sites) cmd_list_sites ;; remove-site) cmd_remove_site "$2" ;; *) print_error "Unknown command: $1" echo "Run 'serverkit help' for usage" exit 1 ;; esac