From 2bdc4b955a6e3a8b3d5be2db82caff3ff5387491 Mon Sep 17 00:00:00 2001 From: Rapeepan Date: Tue, 10 Mar 2026 22:26:04 +0700 Subject: [PATCH 01/37] feat: Add `uninstall.sh` script and update `install.sh` to improve Python version detection and remove app template installation. --- install.sh | 439 ++++++++++++++++++++++++++------------------------- uninstall.sh | 164 +++++++++++++++++++ 2 files changed, 389 insertions(+), 214 deletions(-) create mode 100644 uninstall.sh diff --git a/install.sh b/install.sh index 2d67d3d4..b0359880 100644 --- a/install.sh +++ b/install.sh @@ -1,22 +1,12 @@ #!/bin/bash # -# ServerKit Quick Install Script for Ubuntu/Debian -# -# Architecture: -# - Backend: Runs directly on host (for full system access) -# - Frontend: Runs in Docker (nginx serving static files) -# -# Usage: curl -fsSL https://serverkit.ai/install.sh | bash +# ServerKit Quick Install Script for Ubuntu/Debian/Fedora # set -e -# Safety: Move to a valid directory first -# Prevents "getcwd: cannot access parent directories" error -# when running from a deleted directory (e.g., after uninstall) cd /tmp 2>/dev/null || cd / || true -# Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -28,323 +18,344 @@ VENV_DIR="$INSTALL_DIR/venv" LOG_DIR="/var/log/serverkit" DATA_DIR="/var/lib/serverkit" +# Python constraints +PYTHON_MIN="3.11" +PYTHON_MAX="3.12" +PYTHON_BIN="" + print_header() { - echo -e "${BLUE}" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo " ServerKit Installer" - echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - echo -e "${NC}" +echo -e "${BLUE}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " ServerKit Installer" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "${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}"; } +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}"; } + +version_ge(){ printf '%s\n%s' "$2" "$1" | sort -C -V; } +version_le(){ printf '%s\n%s' "$1" "$2" | sort -C -V; } + +detect_python(){ + +if command -v python3 &>/dev/null; then + PY_VER=$(python3 -c 'import sys;print(".".join(map(str,sys.version_info[:2])))') + + if version_ge "$PY_VER" "$PYTHON_MIN" && version_le "$PY_VER" "$PYTHON_MAX"; then + PYTHON_BIN="python3" + print_success "Using system Python $PY_VER" + return + fi +fi + +print_warning "System Python not supported ($PYTHON_MIN-$PYTHON_MAX)" +} print_header -# Check if running as root if [ "$EUID" -ne 0 ]; then - print_error "Please run as root (sudo)" - exit 1 +print_error "Please run as root (sudo)" +exit 1 fi -# Check Ubuntu/Debian +OS_FAMILY="unknown" + if [ -f /etc/os-release ]; then - . /etc/os-release - if [ "$ID" != "ubuntu" ] && [ "$ID" != "debian" ]; then - print_warning "This script is designed for Ubuntu/Debian. Proceed with caution." - fi +. /etc/os-release + +if [ "$ID" = "ubuntu" ] || [ "$ID" = "debian" ]; then +OS_FAMILY="debian" +elif [ "$ID" = "fedora" ]; then +OS_FAMILY="fedora" +else +print_warning "Unknown OS" +fi fi -echo "" print_info "Installing system dependencies..." -# Configure needrestart for non-interactive mode (Ubuntu 22.04+) -# This prevents the "Which services should be restarted?" dialog -# and avoids dpkg lock issues during automated installs +if [ "$OS_FAMILY" = "debian" ] || [ "$OS_FAMILY" = "unknown" ]; then + export NEEDRESTART_MODE=a export DEBIAN_FRONTEND=noninteractive -# Also configure needrestart.conf if it exists for future apt operations -if [ -f /etc/needrestart/needrestart.conf ]; then - # Set needrestart to auto-restart mode - sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf 2>/dev/null || true -fi - -# Update package list apt-get update -# Install Python and required packages apt-get install -y \ - python3 \ - python3-pip \ - python3-venv \ - python3-dev \ - git \ - curl \ - build-essential \ - libffi-dev \ - libssl-dev \ - iproute2 \ - procps +python3 \ +python3-pip \ +python3-venv \ +python3-dev \ +git \ +curl \ +build-essential \ +libffi-dev \ +libssl-dev \ +iproute2 \ +procps + +detect_python + +if [ -z "$PYTHON_BIN" ]; then + +print_info "Installing Python 3.12..." + +apt-get install -y \ +python3.12 \ +python3.12-venv \ +python3.12-dev + +PYTHON_BIN="python3.12" + +fi + +elif [ "$OS_FAMILY" = "fedora" ]; then + +dnf update -y + +dnf install -y \ +python3 \ +python3-pip \ +git \ +curl \ +gcc \ +gcc-c++ \ +make \ +libffi-devel \ +openssl-devel \ +python3-devel \ +iproute \ +procps-ng + +detect_python + +if [ -z "$PYTHON_BIN" ]; then + +print_info "Installing Python 3.12..." + +dnf install -y \ +python3.12 \ +python3.12-devel + +PYTHON_BIN="python3.12" + +fi + +fi print_success "System dependencies installed" -# Install Docker if not present if ! command -v docker &> /dev/null; then - print_info "Installing Docker..." - curl -fsSL https://get.docker.com | sh - systemctl enable docker - systemctl start docker - print_success "Docker installed" + +print_info "Installing Docker..." + +curl -fsSL https://get.docker.com | sh + +systemctl enable docker +systemctl start docker + +print_success "Docker installed" + else - print_success "Docker already installed" + +print_success "Docker already installed" + fi -# Install Docker Compose plugin if not present if ! docker compose version &> /dev/null; then - print_info "Installing Docker Compose..." - apt-get install -y docker-compose-plugin - print_success "Docker Compose installed" + +print_info "Installing Docker Compose..." + +if [ "$OS_FAMILY" = "fedora" ]; then +dnf install -y docker-compose-plugin +else +apt-get install -y docker-compose-plugin +fi + +print_success "Docker Compose installed" + else - print_success "Docker Compose already installed" + +print_success "Docker Compose already installed" + fi -# Install Node.js for frontend build (builds on host to avoid Docker memory issues) if ! command -v node &> /dev/null; then - print_info "Installing Node.js..." - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y nodejs - print_success "Node.js $(node --version) installed" + +print_info "Installing Node.js..." + +if [ "$OS_FAMILY" = "fedora" ]; then + +curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - + +dnf install -y nodejs + +else + +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + +apt-get install -y nodejs + +fi + +print_success "Node.js $(node --version) installed" + else - print_success "Node.js $(node --version) already installed" + +print_success "Node.js $(node --version) already installed" + fi -# Clone or update repository print_info "Installing ServerKit to $INSTALL_DIR..." if [ -d "$INSTALL_DIR" ]; then - print_warning "Directory exists, updating..." - cd "$INSTALL_DIR" - git fetch origin - git reset --hard origin/main + +print_warning "Directory exists, updating..." + +cd "$INSTALL_DIR" + +git fetch origin +git reset --hard origin/main + else - git clone https://github.com/jhd3197/serverkit.git "$INSTALL_DIR" - cd "$INSTALL_DIR" -fi -print_success "Repository cloned" +git clone https://github.com/jhd3197/serverkit.git "$INSTALL_DIR" + +cd "$INSTALL_DIR" + +fi -# Create directories -print_info "Creating directories..." mkdir -p "$LOG_DIR" mkdir -p "$DATA_DIR" mkdir -p "$INSTALL_DIR/backend/instance" mkdir -p "$INSTALL_DIR/nginx/ssl" -mkdir -p /etc/serverkit/templates -mkdir -p /var/serverkit/apps - -# Copy bundled templates to system directory -print_info "Installing app templates..." -if [ -d "$INSTALL_DIR/backend/templates" ]; then - 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 - print_success "Installed $(ls /etc/serverkit/templates/*.yaml 2>/dev/null | wc -l) app templates" -fi -# Set up Python virtual environment print_info "Setting up Python virtual environment..." -python3 -m venv "$VENV_DIR" + +print_info "Using Python binary: $PYTHON_BIN" + +$PYTHON_BIN --version + +$PYTHON_BIN -m venv "$VENV_DIR" + source "$VENV_DIR/bin/activate" -# Install Python dependencies print_info "Installing Python dependencies..." + pip install --upgrade pip + pip install -r "$INSTALL_DIR/backend/requirements.txt" + pip install gunicorn gevent gevent-websocket print_success "Python dependencies installed" -# Generate .env if not exists if [ ! -f "$INSTALL_DIR/.env" ]; then - print_info "Generating configuration..." - SECRET_KEY=$(openssl rand -hex 32) - JWT_SECRET_KEY=$(openssl rand -hex 32) - cat > "$INSTALL_DIR/.env" << EOF -# ServerKit Configuration -# Generated on $(date) +print_info "Generating configuration..." -# Security Keys (auto-generated, keep secret!) +SECRET_KEY=$(openssl rand -hex 32) +JWT_SECRET_KEY=$(openssl rand -hex 32) + +cat > "$INSTALL_DIR/.env" << EOF SECRET_KEY=$SECRET_KEY JWT_SECRET_KEY=$JWT_SECRET_KEY - -# Database (SQLite by default) DATABASE_URL=sqlite:///$INSTALL_DIR/backend/instance/serverkit.db - -# CORS Origins (comma-separated, add your domain) -CORS_ORIGINS=http://localhost,https://localhost - -# Ports PORT=80 SSL_PORT=443 - -# Environment FLASK_ENV=production EOF - print_success "Configuration generated" -else - print_warning ".env already exists, keeping existing configuration" -fi - -# Generate self-signed SSL certificate if not exists -if [ ! -f "$INSTALL_DIR/nginx/ssl/fullchain.pem" ]; then - print_info "Generating self-signed SSL certificate..." - openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout "$INSTALL_DIR/nginx/ssl/privkey.pem" \ - -out "$INSTALL_DIR/nginx/ssl/fullchain.pem" \ - -subj "/CN=localhost" 2>/dev/null - print_warning "Self-signed certificate created. Replace with real cert for production." fi -# Install systemd service for backend print_info "Installing systemd service..." + cp "$INSTALL_DIR/serverkit-backend.service" /etc/systemd/system/serverkit.service -# Reload systemd and enable service systemctl daemon-reload systemctl enable serverkit print_success "Systemd service installed" -# Make CLI executable and create symlink chmod +x "$INSTALL_DIR/serverkit" + ln -sf "$INSTALL_DIR/serverkit" /usr/local/bin/serverkit print_success "CLI installed" -# Install and configure host nginx as reverse proxy -print_info "Setting up nginx reverse proxy..." -apt-get install -y nginx - -# Stop nginx and remove default site -systemctl stop nginx 2>/dev/null || true -rm -f /etc/nginx/sites-enabled/default +print_info "Installing nginx..." -# Ensure sites directories exist -mkdir -p /etc/nginx/sites-available -mkdir -p /etc/nginx/sites-enabled - -# Ensure nginx.conf includes sites-enabled -if ! grep -q "sites-enabled" /etc/nginx/nginx.conf; then - sed -i '/http {/a \ include /etc/nginx/sites-enabled/*;' /etc/nginx/nginx.conf +if [ "$OS_FAMILY" = "fedora" ]; then +dnf install -y nginx +else +apt-get install -y nginx fi -# Install ServerKit site config -cp "$INSTALL_DIR/nginx/sites-available/serverkit.conf" /etc/nginx/sites-available/ -ln -sf /etc/nginx/sites-available/serverkit.conf /etc/nginx/sites-enabled/ +systemctl enable nginx +systemctl start nginx -# Copy site template -cp "$INSTALL_DIR/nginx/sites-available/example.conf.template" /etc/nginx/sites-available/ +print_success "Nginx installed" -print_success "Nginx proxy configured" +print_info "Building frontend..." -# Clean up Docker to prevent issues -print_info "Cleaning up Docker..." -docker network prune -f 2>/dev/null || true -docker container prune -f 2>/dev/null || true +cd "$INSTALL_DIR/frontend" -# Ensure swap exists for low-RAM VPS servers (Vite build needs ~512MB+) -SWAP_TOTAL=$(free -m | awk '/^Swap:/ {print $2}') -if [ "$SWAP_TOTAL" -lt 512 ]; then - print_info "Creating swap space for build..." - if [ ! -f /swapfile ]; then - fallocate -l 1G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=1024 status=none - chmod 600 /swapfile - mkswap /swapfile >/dev/null - fi - swapon /swapfile 2>/dev/null || true -fi +npm ci --prefer-offline -# Build frontend on host (avoids Docker memory overhead on low-RAM VPS) -print_info "Building frontend..." -cd "$INSTALL_DIR/frontend" -npm ci --prefer-offline 2>&1 | tail -1 NODE_OPTIONS="--max-old-space-size=1024" npm run build -print_success "Frontend built" -# Package frontend into nginx container -print_info "Building frontend container..." cd "$INSTALL_DIR" -docker compose build -print_info "Starting services..." +docker compose build -# Start backend (systemd) systemctl start serverkit -# Start frontend (Docker) docker compose up -d -# Start nginx -systemctl start nginx -systemctl enable nginx - -# Wait for services to start -print_info "Waiting for services to start..." sleep 10 -# Health check echo "" + BACKEND_OK=false FRONTEND_OK=false if curl -s http://127.0.0.1:5000/api/v1/system/health > /dev/null 2>&1; then - BACKEND_OK=true - print_success "Backend is running" +BACKEND_OK=true +print_success "Backend is running" else - print_error "Backend health check failed" +print_error "Backend health check failed" fi if curl -s http://localhost > /dev/null 2>&1; then - FRONTEND_OK=true - print_success "Frontend is running" +FRONTEND_OK=true +print_success "Frontend is running" else - print_error "Frontend health check failed" +print_error "Frontend health check failed" fi echo "" + if [ "$BACKEND_OK" = true ] && [ "$FRONTEND_OK" = true ]; then - # Track successful install - INSTALLED_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null | tr -d '\n\r ') - curl -s "https://serverkit.ai/track/install?v=${INSTALLED_VERSION}" >/dev/null 2>&1 || true - - echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${GREEN} Installation Complete!${NC}" - echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo "" - echo "ServerKit is now running at: http://localhost" - echo "" - echo "Quick Start:" - echo " 1. Create admin user: serverkit create-admin" - echo " 2. View status: serverkit status" - echo " 3. View logs: serverkit logs" - echo "" - echo "Service Management:" - echo " Backend (systemd): systemctl [start|stop|restart] serverkit" - echo " Frontend (Docker): docker compose -C $INSTALL_DIR [up|down]" - echo "" - echo "For all commands: serverkit help" - echo "" + +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN} Installation Complete!${NC}" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +echo "" +echo "ServerKit running at http://localhost" +echo "" +echo "Create admin:" +echo "serverkit create-admin" +echo "" +echo "Status:" +echo "serverkit status" +echo "" + else - echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${RED} Installation may have issues${NC}" - echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo "" - echo "Troubleshooting:" - echo " Backend logs: journalctl -u serverkit -f" - echo " Frontend logs: docker compose -C $INSTALL_DIR logs -f" - echo "" + +echo -e "${RED}Installation may have issues${NC}" + fi diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 00000000..3edb07b8 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +######################################## +# ServerKit Uninstaller +######################################## + +INSTALL_DIR="/opt/serverkit" +DATA_DIR="/var/lib/serverkit" +LOG_DIR="/var/log/serverkit" +LOG_FILE="/var/log/serverkit-uninstall.log" + +mkdir -p /var/log +exec > >(tee -a "$LOG_FILE") 2>&1 + +######################################## +# Colors +######################################## + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +######################################## +# UI helpers +######################################## + +print_header() { +echo -e "${BLUE}" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " ServerKit Uninstaller" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo -e "${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}"; } + +######################################## +# Root check +######################################## + +if [[ $EUID -ne 0 ]]; then +print_error "Please run as root (sudo)" +exit 1 +fi + +print_header + +######################################## +# Confirm uninstall +######################################## + +echo +read -p "Remove ServerKit completely? (y/N): " confirm + +if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then +print_warning "Uninstall cancelled" +exit 0 +fi + +######################################## +# Stop services +######################################## + +print_info "Stopping ServerKit service" + +systemctl stop serverkit 2>/dev/null || true +systemctl disable serverkit 2>/dev/null || true + +print_success "Backend service stopped" + +######################################## +# Stop containers +######################################## + +print_info "Stopping Docker containers" + +docker compose -C "$INSTALL_DIR" down --remove-orphans 2>/dev/null || true + +print_success "Containers removed" + +######################################## +# Remove systemd service +######################################## + +print_info "Removing systemd service" + +rm -f /etc/systemd/system/serverkit.service + +systemctl daemon-reload + +print_success "Systemd service removed" + +######################################## +# Remove nginx config +######################################## + +print_info "Removing nginx configuration" + +rm -f /etc/nginx/sites-enabled/serverkit.conf 2>/dev/null || true +rm -f /etc/nginx/sites-available/serverkit.conf 2>/dev/null || true + +systemctl reload nginx 2>/dev/null || true + +print_success "Nginx config removed" + +######################################## +# Remove files +######################################## + +print_info "Removing installation files" + +rm -rf "$INSTALL_DIR" + +print_success "Installation directory removed" + +######################################## +# Remove data +######################################## + +print_info "Removing data directory" + +rm -rf "$DATA_DIR" + +print_success "Data directory removed" + +######################################## +# Remove logs +######################################## + +print_info "Removing logs" + +rm -rf "$LOG_DIR" + +print_success "Log directory removed" + +######################################## +# Remove CLI +######################################## + +print_info "Removing CLI command" + +rm -f /usr/local/bin/serverkit + +print_success "CLI removed" + +######################################## +# Finish +######################################## + +echo +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN} ServerKit removed successfully${NC}" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +echo +echo "Uninstall log:" +echo "$LOG_FILE" +echo \ No newline at end of file From 600dd5325e3227511afa1e14bc8e889648eef1ac Mon Sep 17 00:00:00 2001 From: Rapeepan Date: Tue, 10 Mar 2026 22:42:40 +0700 Subject: [PATCH 02/37] feat: Add low RAM detection with safe mode, swap setup, and optimize post-installation steps. --- install.sh | 52 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/install.sh b/install.sh index b0359880..dc72ac27 100644 --- a/install.sh +++ b/install.sh @@ -23,6 +23,8 @@ PYTHON_MIN="3.11" PYTHON_MAX="3.12" PYTHON_BIN="" +SAFE_MODE=false + print_header() { echo -e "${BLUE}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @@ -54,6 +56,39 @@ fi print_warning "System Python not supported ($PYTHON_MIN-$PYTHON_MAX)" } +check_ram(){ + +RAM=$(free -m | awk '/Mem:/ {print $2}') + +if [ "$RAM" -le 700 ]; then +SAFE_MODE=true +print_warning "Low RAM detected (${RAM}MB) → enabling VPS Safe Mode" +fi + +} + +setup_swap(){ + +SWAP_TOTAL=$(free -m | awk '/Swap:/ {print $2}') + +if [ "$SWAP_TOTAL" -lt 512 ]; then + +print_info "Creating swap (1GB)" + +if [ ! -f /swapfile ]; then +fallocate -l 1G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=1024 +chmod 600 /swapfile +mkswap /swapfile >/dev/null +fi + +swapon /swapfile || true + +print_success "Swap enabled" + +fi + +} + print_header if [ "$EUID" -ne 0 ]; then @@ -188,15 +223,10 @@ if ! command -v node &> /dev/null; then print_info "Installing Node.js..." if [ "$OS_FAMILY" = "fedora" ]; then - curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - - dnf install -y nodejs - else - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y nodejs fi @@ -216,14 +246,12 @@ if [ -d "$INSTALL_DIR" ]; then print_warning "Directory exists, updating..." cd "$INSTALL_DIR" - git fetch origin git reset --hard origin/main else git clone https://github.com/jhd3197/serverkit.git "$INSTALL_DIR" - cd "$INSTALL_DIR" fi @@ -245,9 +273,13 @@ source "$VENV_DIR/bin/activate" print_info "Installing Python dependencies..." +if [ "$SAFE_MODE" = true ]; then +pip install --upgrade pip --no-cache-dir +pip install --no-cache-dir -r "$INSTALL_DIR/backend/requirements.txt" +else pip install --upgrade pip - pip install -r "$INSTALL_DIR/backend/requirements.txt" +fi pip install gunicorn gevent gevent-websocket @@ -315,9 +347,9 @@ systemctl start serverkit docker compose up -d -sleep 10 +systemctl start nginx -echo "" +sleep 8 BACKEND_OK=false FRONTEND_OK=false From 6e9145495d9e91dedfbff7e65ef9a46b16442887 Mon Sep 17 00:00:00 2001 From: Juan Denis <13461850+jhd3197@users.noreply.github.com> Date: Sun, 15 Mar 2026 18:05:16 -0400 Subject: [PATCH 03/37] Refactor installer and uninstaller scripts Major refactor of install.sh and uninstall.sh to improve robustness, cross-distro support, and low-RAM VPS behavior. install.sh: add safety cd, colored helpers, Python detection with fallback to 3.12, RAM check and swap setup, noninteractive apt/dnf and needrestart handling, Docker/Docker Compose/Node installation, clone/update repo, create system dirs and install bundled templates, set up venv and dependencies, generate .env, create self-signed SSL, install systemd service, configure nginx reverse proxy, build frontend on host to avoid Docker memory issues, start services, perform health checks, and send install telemetry. uninstall.sh: formatting/consistency fixes, require root, safer docker compose teardown, remove /etc/serverkit and /var/serverkit, and send uninstall telemetry. Also add CONTRIBUTORS.md and remove the .claude code-review SKILL.md. --- .claude/skills/code-review/SKILL.md | 165 --------- CONTRIBUTORS.md | 14 + install.sh | 545 ++++++++++++++++------------ uninstall.sh | 44 ++- 4 files changed, 348 insertions(+), 420 deletions(-) delete mode 100644 .claude/skills/code-review/SKILL.md create mode 100644 CONTRIBUTORS.md diff --git a/.claude/skills/code-review/SKILL.md b/.claude/skills/code-review/SKILL.md deleted file mode 100644 index 3aa49c32..00000000 --- a/.claude/skills/code-review/SKILL.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -name: code-review -description: Run a multi-perspective code review on recent changes using specialized reviewer personas. Each reviewer focuses on their domain — security, ops, cross-platform, API design, frontend, and performance. Produces a dated review report. -argument-hint: "[file, directory, or git range]" ---- - -# Code Review — Multi-Perspective Audit - -Review code from the perspective of 6 specialized reviewers, each with their own focus area. Produces a structured report in `.reviews/`. - -## Scope - -Determine what to review based on the argument: - -- **No argument**: Review all uncommitted changes (`git diff` + `git diff --cached`) -- **A file or directory path**: Review that specific path -- **A git range** (e.g., `main..HEAD`, `HEAD~3`): Review that commit range -- **`last`**: Review the last commit (`HEAD~1..HEAD`) - -## The Reviewers - -### Mara — Security - -Focuses on authentication, authorization, and injection vectors. - -- JWT handling: token validation, expiry, refresh flows -- SQL injection: raw queries, unparameterized input -- XSS: unsanitized user input rendered in responses or frontend -- CORS misconfigurations -- Path traversal in file operations (`../` in user-supplied paths) -- Secrets in code (API keys, passwords, hardcoded tokens) -- Missing `@jwt_required()` on API routes that need auth -- CSRF protection gaps - -### Raj — Ops & Infrastructure - -Focuses on subprocess calls, privilege escalation, and system operations. - -- Missing `sudo` on privileged commands (systemctl, firewall-cmd, ufw, certbot, nginx) -- Raw `subprocess.run()` that should use `run_privileged()`, `ServiceControl`, or `PackageManager` -- Missing error handling on subprocess calls (unchecked `returncode`, no try/except) -- Hardcoded paths that assume specific filesystem layout -- Logging gaps: operations that modify system state without logging what they did -- Service restart ordering issues - -### Sol — Cross-Platform & Containers - -Focuses on portability across distros and container environments. - -- Distro-specific commands (`dpkg`, `apt`, `rpm`, `dnf`) without `FileNotFoundError` handling -- Unguarded `/proc/` and `/sys/` reads that fail in LXC/containers -- `psutil` calls that return `None` in containers (`disk_io_counters`, `sensors_temperatures`) used without null checks -- Docker socket assumptions without availability checks -- Firewall commands that need `CAP_NET_ADMIN` (dropped in unprivileged LXC) -- Hardcoded `/dev/` device paths -- Assumptions about init system (systemd vs others) -- Package manager detection that only checks one family - -### Kai — API Design - -Focuses on REST conventions, error handling, and request/response patterns. - -- Inconsistent error response format (should always be `{'error': 'message'}, status_code`) -- Missing input validation on request body fields -- Wrong HTTP status codes (e.g., 200 on error, 404 when it should be 403) -- Missing pagination on list endpoints -- Endpoints that return too much data (no field filtering) -- Inconsistent URL naming (`/api/v1/` prefix, plural nouns) -- Missing rate limiting on sensitive endpoints (login, password reset) -- N+1 query patterns in endpoints that return lists - -### Lena — Frontend - -Focuses on React patterns, UX quality, and style consistency. - -- Class components or non-hook patterns (should be functional + hooks only) -- Inline styles (should use LESS with design system variables) -- Missing loading states or error handling on data fetches -- Accessibility gaps: missing alt text, aria labels, keyboard navigation -- Hardcoded strings that should come from the API or config -- Memory leaks: missing cleanup in `useEffect` -- Props drilling beyond 3 levels (should use Context) -- Missing key props in lists -- Console.log left in production code - -### Omar — Performance - -Focuses on efficiency, caching, and resource usage. - -- Synchronous blocking calls that should be async (especially subprocess calls in request handlers) -- Missing database indexes on frequently queried columns -- Unbounded queries: `SELECT *` without `LIMIT` or pagination -- Repeated identical subprocess calls that could be cached -- Large file reads loaded entirely into memory -- Frontend: unnecessary re-renders, missing `useMemo`/`useCallback` where expensive -- Missing `timeout` on subprocess calls or external HTTP requests -- Socket.IO events that broadcast too frequently - -## How to Review - -1. Determine the scope from `$ARGUMENTS` and read all relevant code -2. For each reviewer, analyze the code **only through their lens** — don't overlap -3. Rate each finding: - - **Fix** — Bug, vulnerability, or will break in production. Must address. - - **Improve** — Works but suboptimal. Should address. - - **Note** — Observation or suggestion. Nice to know, no action needed. -4. Collect all findings - -## Report - -Create the `.reviews/` directory if it doesn't exist. Write to `.reviews/YYYY-MM-DD-review.md` (use today's date). If a file for today exists, append a counter: `YYYY-MM-DD-2-review.md`. - -```markdown -# Code Review — YYYY-MM-DD - -**Scope:** -**Files reviewed:** N - -## Summary - -| Reviewer | Focus | Fix | Improve | Note | -|----------|-------|-----|---------|------| -| Mara | Security | N | N | N | -| Raj | Ops | N | N | N | -| Sol | Cross-Platform | N | N | N | -| Kai | API Design | N | N | N | -| Lena | Frontend | N | N | N | -| Omar | Performance | N | N | N | -| **Total** | | **N** | **N** | **N** | - -## Findings - -### Mara — Security - -#### [Fix] Short description -`file_path:line_number` -Explanation of the issue and why it matters. -**Suggested fix:** What to change. - -#### [Improve] Short description -... - -### Raj — Ops & Infrastructure -... - -### Sol — Cross-Platform & Containers -... - -### Kai — API Design -... - -### Lena — Frontend -... - -### Omar — Performance -... - -## Verdict - -**PASS** — No Fix-severity findings. Ship it. -or -**NEEDS WORK** — N Fix-severity findings must be addressed before merging. -``` - -After writing the report, print the summary table and verdict to the user. If there are Fix-severity findings, ask if they want you to address them now. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 00000000..9e7d57a6 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,14 @@ +# Contributors + +Thanks to everyone who has contributed to ServerKit! + +## Core + +- **Juan Denis** ([@jhd3197](https://github.com/jhd3197)) — Creator and maintainer + +## Contributors + +| Who | Contribution | PR | +|-----|-------------|-----| +| **Rapeepan Moonthai** ([@rapeeza1598](https://github.com/rapeeza1598)) | Fedora support for install script (DNF + SELinux) | [#31](https://github.com/jhd3197/ServerKit/pull/31) | +| **Piya Miang-Lae** ([@Piya-Boy](https://github.com/Piya-Boy)) | Backups storage provider | [#26](https://github.com/jhd3197/ServerKit/pull/26) | diff --git a/install.sh b/install.sh index dc72ac27..64cf8989 100644 --- a/install.sh +++ b/install.sh @@ -2,11 +2,21 @@ # # ServerKit Quick Install Script for Ubuntu/Debian/Fedora # +# Architecture: +# - Backend: Runs directly on host (for full system access) +# - Frontend: Runs in Docker (nginx serving static files) +# +# Usage: curl -fsSL https://serverkit.ai/install.sh | bash +# set -e +# Safety: Move to a valid directory first +# Prevents "getcwd: cannot access parent directories" error +# when running from a deleted directory (e.g., after uninstall) cd /tmp 2>/dev/null || cd / || true +# Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -26,368 +36,425 @@ PYTHON_BIN="" SAFE_MODE=false print_header() { -echo -e "${BLUE}" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo " ServerKit Installer" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo -e "${NC}" + echo -e "${BLUE}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " ServerKit Installer" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "${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}"; } - -version_ge(){ printf '%s\n%s' "$2" "$1" | sort -C -V; } -version_le(){ printf '%s\n%s' "$1" "$2" | sort -C -V; } - -detect_python(){ - -if command -v python3 &>/dev/null; then - PY_VER=$(python3 -c 'import sys;print(".".join(map(str,sys.version_info[:2])))') - - if version_ge "$PY_VER" "$PYTHON_MIN" && version_le "$PY_VER" "$PYTHON_MAX"; then - PYTHON_BIN="python3" - print_success "Using system Python $PY_VER" - return +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}"; } + +version_ge() { printf '%s\n%s' "$2" "$1" | sort -C -V; } +version_le() { printf '%s\n%s' "$1" "$2" | sort -C -V; } + +# Detect supported Python (3.11-3.12) +detect_python() { + if command -v python3 &>/dev/null; then + PY_VER=$(python3 -c 'import sys;print(".".join(map(str,sys.version_info[:2])))') + if version_ge "$PY_VER" "$PYTHON_MIN" && version_le "$PY_VER" "$PYTHON_MAX"; then + PYTHON_BIN="python3" + print_success "Using system Python $PY_VER" + return + fi fi -fi - -print_warning "System Python not supported ($PYTHON_MIN-$PYTHON_MAX)" + print_warning "System Python not in supported range ($PYTHON_MIN-$PYTHON_MAX)" } -check_ram(){ - -RAM=$(free -m | awk '/Mem:/ {print $2}') - -if [ "$RAM" -le 700 ]; then -SAFE_MODE=true -print_warning "Low RAM detected (${RAM}MB) → enabling VPS Safe Mode" -fi - +# Check RAM and enable safe mode for small VPS +check_ram() { + RAM=$(free -m | awk '/Mem:/ {print $2}') + if [ "$RAM" -le 700 ]; then + SAFE_MODE=true + print_warning "Low RAM detected (${RAM}MB) → enabling VPS Safe Mode" + fi } -setup_swap(){ - -SWAP_TOTAL=$(free -m | awk '/Swap:/ {print $2}') - -if [ "$SWAP_TOTAL" -lt 512 ]; then - -print_info "Creating swap (1GB)" - -if [ ! -f /swapfile ]; then -fallocate -l 1G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=1024 -chmod 600 /swapfile -mkswap /swapfile >/dev/null -fi - -swapon /swapfile || true - -print_success "Swap enabled" - -fi - +# Create swap if system has very little +setup_swap() { + SWAP_TOTAL=$(free -m | awk '/^Swap:/ {print $2}') + if [ "$SWAP_TOTAL" -lt 512 ]; then + print_info "Creating swap space (1GB)..." + if [ ! -f /swapfile ]; then + fallocate -l 1G /swapfile 2>/dev/null || dd if=/dev/zero of=/swapfile bs=1M count=1024 status=none + chmod 600 /swapfile + mkswap /swapfile >/dev/null + fi + swapon /swapfile 2>/dev/null || true + print_success "Swap enabled" + fi } print_header +# Check if running as root if [ "$EUID" -ne 0 ]; then -print_error "Please run as root (sudo)" -exit 1 + print_error "Please run as root (sudo)" + exit 1 fi -OS_FAMILY="unknown" +# Enable low-RAM protections early (needs root for swap) +check_ram +setup_swap +# Detect OS family +OS_FAMILY="unknown" if [ -f /etc/os-release ]; then -. /etc/os-release - -if [ "$ID" = "ubuntu" ] || [ "$ID" = "debian" ]; then -OS_FAMILY="debian" -elif [ "$ID" = "fedora" ]; then -OS_FAMILY="fedora" + . /etc/os-release + if [ "$ID" = "ubuntu" ] || [ "$ID" = "debian" ]; then + OS_FAMILY="debian" + elif [ "$ID" = "fedora" ]; then + OS_FAMILY="fedora" + else + print_warning "Unsupported OS ($ID). This script is designed for Ubuntu/Debian/Fedora." + fi else -print_warning "Unknown OS" -fi + print_warning "Cannot detect OS. Proceeding with caution." fi +echo "" print_info "Installing system dependencies..." if [ "$OS_FAMILY" = "debian" ] || [ "$OS_FAMILY" = "unknown" ]; then + # Configure needrestart for non-interactive mode (Ubuntu 22.04+) + # This prevents the "Which services should be restarted?" dialog + # and avoids dpkg lock issues during automated installs + export NEEDRESTART_MODE=a + export DEBIAN_FRONTEND=noninteractive + + # Also configure needrestart.conf if it exists for future apt operations + if [ -f /etc/needrestart/needrestart.conf ]; then + sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/" /etc/needrestart/needrestart.conf 2>/dev/null || true + fi -export NEEDRESTART_MODE=a -export DEBIAN_FRONTEND=noninteractive - -apt-get update - -apt-get install -y \ -python3 \ -python3-pip \ -python3-venv \ -python3-dev \ -git \ -curl \ -build-essential \ -libffi-dev \ -libssl-dev \ -iproute2 \ -procps - -detect_python - -if [ -z "$PYTHON_BIN" ]; then - -print_info "Installing Python 3.12..." - -apt-get install -y \ -python3.12 \ -python3.12-venv \ -python3.12-dev - -PYTHON_BIN="python3.12" - -fi + apt-get update + + apt-get install -y \ + python3 \ + python3-pip \ + python3-venv \ + python3-dev \ + git \ + curl \ + build-essential \ + libffi-dev \ + libssl-dev \ + iproute2 \ + procps + + detect_python + + # If system Python is out of supported range, install 3.12 + if [ -z "$PYTHON_BIN" ]; then + print_info "Installing Python 3.12..." + apt-get install -y \ + python3.12 \ + python3.12-venv \ + python3.12-dev + PYTHON_BIN="python3.12" + fi elif [ "$OS_FAMILY" = "fedora" ]; then - -dnf update -y - -dnf install -y \ -python3 \ -python3-pip \ -git \ -curl \ -gcc \ -gcc-c++ \ -make \ -libffi-devel \ -openssl-devel \ -python3-devel \ -iproute \ -procps-ng - -detect_python - -if [ -z "$PYTHON_BIN" ]; then - -print_info "Installing Python 3.12..." - -dnf install -y \ -python3.12 \ -python3.12-devel - -PYTHON_BIN="python3.12" - -fi - + dnf update -y + + dnf install -y \ + python3 \ + python3-pip \ + python3-devel \ + git \ + curl \ + gcc \ + gcc-c++ \ + make \ + libffi-devel \ + openssl-devel \ + python3-devel \ + iproute \ + procps-ng + + detect_python + + if [ -z "$PYTHON_BIN" ]; then + print_info "Installing Python 3.12..." + dnf install -y \ + python3.12 \ + python3.12-devel + PYTHON_BIN="python3.12" + fi fi print_success "System dependencies installed" +# Install Docker if not present if ! command -v docker &> /dev/null; then - -print_info "Installing Docker..." - -curl -fsSL https://get.docker.com | sh - -systemctl enable docker -systemctl start docker - -print_success "Docker installed" - + print_info "Installing Docker..." + curl -fsSL https://get.docker.com | sh + systemctl enable docker + systemctl start docker + print_success "Docker installed" else - -print_success "Docker already installed" - + print_success "Docker already installed" fi +# Install Docker Compose plugin if not present if ! docker compose version &> /dev/null; then - -print_info "Installing Docker Compose..." - -if [ "$OS_FAMILY" = "fedora" ]; then -dnf install -y docker-compose-plugin -else -apt-get install -y docker-compose-plugin -fi - -print_success "Docker Compose installed" - + print_info "Installing Docker Compose..." + if [ "$OS_FAMILY" = "fedora" ]; then + dnf install -y docker-compose-plugin + else + apt-get install -y docker-compose-plugin + fi + print_success "Docker Compose installed" else - -print_success "Docker Compose already installed" - + print_success "Docker Compose already installed" fi +# Install Node.js for frontend build (builds on host to avoid Docker memory issues) if ! command -v node &> /dev/null; then - -print_info "Installing Node.js..." - -if [ "$OS_FAMILY" = "fedora" ]; then -curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - -dnf install -y nodejs -else -curl -fsSL https://deb.nodesource.com/setup_20.x | bash - -apt-get install -y nodejs - -fi - -print_success "Node.js $(node --version) installed" - + print_info "Installing Node.js..." + if [ "$OS_FAMILY" = "fedora" ]; then + curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - + dnf install -y nodejs + else + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y nodejs + fi + print_success "Node.js $(node --version) installed" else - -print_success "Node.js $(node --version) already installed" - + print_success "Node.js $(node --version) already installed" fi +# Clone or update repository print_info "Installing ServerKit to $INSTALL_DIR..." if [ -d "$INSTALL_DIR" ]; then - -print_warning "Directory exists, updating..." - -cd "$INSTALL_DIR" -git fetch origin -git reset --hard origin/main - + print_warning "Directory exists, updating..." + cd "$INSTALL_DIR" + git fetch origin + git reset --hard origin/main else - -git clone https://github.com/jhd3197/serverkit.git "$INSTALL_DIR" -cd "$INSTALL_DIR" - + git clone https://github.com/jhd3197/serverkit.git "$INSTALL_DIR" + cd "$INSTALL_DIR" fi +print_success "Repository cloned" + +# Create directories +print_info "Creating directories..." mkdir -p "$LOG_DIR" mkdir -p "$DATA_DIR" mkdir -p "$INSTALL_DIR/backend/instance" mkdir -p "$INSTALL_DIR/nginx/ssl" +mkdir -p /etc/serverkit/templates +mkdir -p /var/serverkit/apps + +# Copy bundled templates to system directory +print_info "Installing app templates..." +if [ -d "$INSTALL_DIR/backend/templates" ]; then + 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 + print_success "Installed $(ls /etc/serverkit/templates/*.yaml 2>/dev/null | wc -l) app templates" +fi +# Set up Python virtual environment print_info "Setting up Python virtual environment..." - -print_info "Using Python binary: $PYTHON_BIN" - -$PYTHON_BIN --version - $PYTHON_BIN -m venv "$VENV_DIR" - source "$VENV_DIR/bin/activate" +# Install Python dependencies print_info "Installing Python dependencies..." - +pip install --upgrade pip if [ "$SAFE_MODE" = true ]; then -pip install --upgrade pip --no-cache-dir -pip install --no-cache-dir -r "$INSTALL_DIR/backend/requirements.txt" + pip install --no-cache-dir -r "$INSTALL_DIR/backend/requirements.txt" else -pip install --upgrade pip -pip install -r "$INSTALL_DIR/backend/requirements.txt" + pip install -r "$INSTALL_DIR/backend/requirements.txt" fi - pip install gunicorn gevent gevent-websocket print_success "Python dependencies installed" +# Generate .env if not exists if [ ! -f "$INSTALL_DIR/.env" ]; then + print_info "Generating configuration..." + SECRET_KEY=$(openssl rand -hex 32) + JWT_SECRET_KEY=$(openssl rand -hex 32) -print_info "Generating configuration..." + cat > "$INSTALL_DIR/.env" << EOF +# ServerKit Configuration +# Generated on $(date) -SECRET_KEY=$(openssl rand -hex 32) -JWT_SECRET_KEY=$(openssl rand -hex 32) - -cat > "$INSTALL_DIR/.env" << EOF +# Security Keys (auto-generated, keep secret!) SECRET_KEY=$SECRET_KEY JWT_SECRET_KEY=$JWT_SECRET_KEY + +# Database (SQLite by default) DATABASE_URL=sqlite:///$INSTALL_DIR/backend/instance/serverkit.db + +# CORS Origins (comma-separated, add your domain) +CORS_ORIGINS=http://localhost,https://localhost + +# Ports PORT=80 SSL_PORT=443 + +# Environment FLASK_ENV=production EOF + print_success "Configuration generated" +else + print_warning ".env already exists, keeping existing configuration" fi -print_info "Installing systemd service..." +# Generate self-signed SSL certificate if not exists +if [ ! -f "$INSTALL_DIR/nginx/ssl/fullchain.pem" ]; then + print_info "Generating self-signed SSL certificate..." + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout "$INSTALL_DIR/nginx/ssl/privkey.pem" \ + -out "$INSTALL_DIR/nginx/ssl/fullchain.pem" \ + -subj "/CN=localhost" 2>/dev/null + print_warning "Self-signed certificate created. Replace with real cert for production." +fi +# Install systemd service for backend +print_info "Installing systemd service..." cp "$INSTALL_DIR/serverkit-backend.service" /etc/systemd/system/serverkit.service +# Reload systemd and enable service systemctl daemon-reload systemctl enable serverkit print_success "Systemd service installed" +# Make CLI executable and create symlink chmod +x "$INSTALL_DIR/serverkit" - ln -sf "$INSTALL_DIR/serverkit" /usr/local/bin/serverkit print_success "CLI installed" -print_info "Installing nginx..." - +# Install and configure host nginx as reverse proxy +print_info "Setting up nginx reverse proxy..." if [ "$OS_FAMILY" = "fedora" ]; then -dnf install -y nginx + dnf install -y nginx else -apt-get install -y nginx + apt-get install -y nginx fi -systemctl enable nginx -systemctl start nginx +# Stop nginx and remove default site +systemctl stop nginx 2>/dev/null || true +rm -f /etc/nginx/sites-enabled/default -print_success "Nginx installed" +# Ensure sites directories exist +mkdir -p /etc/nginx/sites-available +mkdir -p /etc/nginx/sites-enabled -print_info "Building frontend..." +# Ensure nginx.conf includes sites-enabled (Fedora uses conf.d by default) +if ! grep -q "sites-enabled" /etc/nginx/nginx.conf; then + sed -i '/http {/a \ include /etc/nginx/sites-enabled/*;' /etc/nginx/nginx.conf +fi -cd "$INSTALL_DIR/frontend" +# Install ServerKit site config +cp "$INSTALL_DIR/nginx/sites-available/serverkit.conf" /etc/nginx/sites-available/ +ln -sf /etc/nginx/sites-available/serverkit.conf /etc/nginx/sites-enabled/ + +# Copy site template +cp "$INSTALL_DIR/nginx/sites-available/example.conf.template" /etc/nginx/sites-available/ + +# Configure SELinux to allow nginx reverse proxying (Fedora) +if [ "$OS_FAMILY" = "fedora" ] && command -v setsebool &> /dev/null; then + setsebool -P httpd_can_network_connect 1 2>/dev/null || true +fi + +print_success "Nginx proxy configured" + +# Clean up Docker to prevent issues +print_info "Cleaning up Docker..." +docker network prune -f 2>/dev/null || true +docker container prune -f 2>/dev/null || true -npm ci --prefer-offline +# Ensure swap exists for low-RAM VPS servers (Vite build needs ~512MB+) +setup_swap +# Build frontend on host (avoids Docker memory overhead on low-RAM VPS) +print_info "Building frontend..." +cd "$INSTALL_DIR/frontend" +npm ci --prefer-offline 2>&1 | tail -1 NODE_OPTIONS="--max-old-space-size=1024" npm run build +print_success "Frontend built" +# Package frontend into nginx container +print_info "Building frontend container..." cd "$INSTALL_DIR" - docker compose build +print_info "Starting services..." + +# Start backend (systemd) systemctl start serverkit +# Start frontend (Docker) docker compose up -d +# Start nginx systemctl start nginx +systemctl enable nginx -sleep 8 +# Wait for services to start +print_info "Waiting for services to start..." +sleep 10 +# Health check +echo "" BACKEND_OK=false FRONTEND_OK=false if curl -s http://127.0.0.1:5000/api/v1/system/health > /dev/null 2>&1; then -BACKEND_OK=true -print_success "Backend is running" + BACKEND_OK=true + print_success "Backend is running" else -print_error "Backend health check failed" + print_error "Backend health check failed" fi if curl -s http://localhost > /dev/null 2>&1; then -FRONTEND_OK=true -print_success "Frontend is running" + FRONTEND_OK=true + print_success "Frontend is running" else -print_error "Frontend health check failed" + print_error "Frontend health check failed" fi echo "" - if [ "$BACKEND_OK" = true ] && [ "$FRONTEND_OK" = true ]; then - -echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${GREEN} Installation Complete!${NC}" -echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - -echo "" -echo "ServerKit running at http://localhost" -echo "" -echo "Create admin:" -echo "serverkit create-admin" -echo "" -echo "Status:" -echo "serverkit status" -echo "" - + # Track successful install + INSTALLED_VERSION=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null | tr -d '\n\r ') + curl -s "https://serverkit.ai/track/install?v=${INSTALLED_VERSION}" >/dev/null 2>&1 || true + + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN} Installation Complete!${NC}" + echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo "ServerKit is now running at: http://localhost" + echo "" + echo "Quick Start:" + echo " 1. Create admin user: serverkit create-admin" + echo " 2. View status: serverkit status" + echo " 3. View logs: serverkit logs" + echo "" + echo "Service Management:" + echo " Backend (systemd): systemctl [start|stop|restart] serverkit" + echo " Frontend (Docker): docker compose --project-directory $INSTALL_DIR [up|down]" + echo "" + echo "For all commands: serverkit help" + echo "" else - -echo -e "${RED}Installation may have issues${NC}" - + echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${RED} Installation may have issues${NC}" + echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo "Troubleshooting:" + echo " Backend logs: journalctl -u serverkit -f" + echo " Frontend logs: docker compose --project-directory $INSTALL_DIR logs -f" + echo "" fi diff --git a/uninstall.sh b/uninstall.sh index 3edb07b8..c2440d2b 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -28,25 +28,25 @@ NC='\033[0m' ######################################## print_header() { -echo -e "${BLUE}" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo " ServerKit Uninstaller" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo -e "${NC}" + echo -e "${BLUE}" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " ServerKit Uninstaller" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo -e "${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}"; } +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}"; } ######################################## # Root check ######################################## if [[ $EUID -ne 0 ]]; then -print_error "Please run as root (sudo)" -exit 1 + print_error "Please run as root (sudo)" + exit 1 fi print_header @@ -59,8 +59,8 @@ echo read -p "Remove ServerKit completely? (y/N): " confirm if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then -print_warning "Uninstall cancelled" -exit 0 + print_warning "Uninstall cancelled" + exit 0 fi ######################################## @@ -80,7 +80,11 @@ print_success "Backend service stopped" print_info "Stopping Docker containers" -docker compose -C "$INSTALL_DIR" down --remove-orphans 2>/dev/null || true +if command -v docker &>/dev/null && [ -d "$INSTALL_DIR" ]; then + docker compose --project-directory "$INSTALL_DIR" down --remove-orphans 2>/dev/null || true +else + print_warning "Docker or install directory not found, skipping container cleanup" +fi print_success "Containers removed" @@ -126,8 +130,10 @@ print_success "Installation directory removed" print_info "Removing data directory" rm -rf "$DATA_DIR" +rm -rf /etc/serverkit +rm -rf /var/serverkit -print_success "Data directory removed" +print_success "Data directories removed" ######################################## # Remove logs @@ -149,6 +155,12 @@ rm -f /usr/local/bin/serverkit print_success "CLI removed" +######################################## +# Track uninstall +######################################## + +curl -s "https://serverkit.ai/track/uninstall" >/dev/null 2>&1 || true + ######################################## # Finish ######################################## @@ -161,4 +173,4 @@ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━ echo echo "Uninstall log:" echo "$LOG_FILE" -echo \ No newline at end of file +echo From 54186f97474437b476ad3f7409753804b61c15c7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 22:05:29 +0000 Subject: [PATCH 04/37] chore: bump version to 1.3.7 [skip ci] --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 95b25aee..3336003d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.6 +1.3.7 From c160536c0a3ae43994893fb9b6829822e7f85dfd Mon Sep 17 00:00:00 2001 From: Juan Denis <13461850+jhd3197@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:57:11 -0400 Subject: [PATCH 05/37] Mark CLI admin as setup; add login fallback UI When creating an admin via the backend CLI, mark setup as complete by calling SettingsService.complete_setup so the web UI won't show the setup wizard. In the frontend, add a sign-in flow to SetupStepAccount when registration is disabled (e.g. an admin was created via CLI): pull login and registrationEnabled from Auth, implement handleLogin, show an informational banner, and keep the original register flow as handleRegister. In AuthContext, make checkSetupStatus retry a few times (with a short delay) if the backend isn't ready, and on exhausted retries assume a fresh install (needsSetup and registrationEnabled = true) to avoid locking out users while the backend initializes. --- backend/cli.py | 4 + .../src/components/setup/SetupStepAccount.jsx | 83 ++++++++++++++++++- frontend/src/contexts/AuthContext.jsx | 15 +++- 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/backend/cli.py b/backend/cli.py index 13deb2e2..52fa31db 100644 --- a/backend/cli.py +++ b/backend/cli.py @@ -78,6 +78,10 @@ def create_admin(email, username, password): db.session.add(user) db.session.commit() + # Mark setup as complete so the UI doesn't show the setup wizard + from app.services.settings_service import SettingsService + SettingsService.complete_setup(user_id=user.id) + click.echo(click.style(f'Admin user "{username}" created successfully!', fg='green')) diff --git a/frontend/src/components/setup/SetupStepAccount.jsx b/frontend/src/components/setup/SetupStepAccount.jsx index e580376b..b34c4706 100644 --- a/frontend/src/components/setup/SetupStepAccount.jsx +++ b/frontend/src/components/setup/SetupStepAccount.jsx @@ -9,9 +9,12 @@ const SetupStepAccount = ({ onComplete }) => { const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); - const { register } = useAuth(); + const { register, login, registrationEnabled } = useAuth(); - async function handleSubmit(e) { + // If users already exist (e.g. admin created via CLI), show login form instead + const showLogin = !registrationEnabled; + + async function handleRegister(e) { e.preventDefault(); setError(''); @@ -37,6 +40,80 @@ const SetupStepAccount = ({ onComplete }) => { } } + async function handleLogin(e) { + e.preventDefault(); + setError(''); + setLoading(true); + + try { + await login(email, password); + onComplete({ email, username: email }); + } catch (err) { + setError(err.message || 'Failed to sign in'); + } finally { + setLoading(false); + } + } + + if (showLogin) { + return ( +
+

Sign In

+

+ An admin account already exists. Sign in to continue setup. +

+ +
+
+ +
+

+ It looks like an admin account was created via the CLI. + Sign in with those credentials to finish setting up your server. +

+
+ + {error &&
{error}
} + +
+
+ + setEmail(e.target.value)} + placeholder="admin@example.com" + required + autoFocus + /> +
+ +
+ + setPassword(e.target.value)} + placeholder="Enter your password" + required + /> +
+ + +
+
+ ); + } + return (

Create Admin Account

@@ -57,7 +134,7 @@ const SetupStepAccount = ({ onComplete }) => { {error &&
{error}
} -
+
0) { + await new Promise(r => setTimeout(r, 2000)); + return checkSetupStatus(retries - 1); + } + // Exhausted retries — assume fresh install so user isn't locked out + setSetupStatus(prev => ({ + ...prev, + needsSetup: true, + registrationEnabled: true, + checked: true + })); await checkAuth(); } } From 51d1b3b4ec574d316e5c96d632f9fb91a1a5d802 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 22 Mar 2026 15:57:25 +0000 Subject: [PATCH 06/37] chore: bump version to 1.3.8 [skip ci] --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3336003d..e05cb332 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.7 +1.3.8 From f4a926a69e157893b44ef1a42771436efd22a83f Mon Sep 17 00:00:00 2001 From: Juan Denis <13461850+jhd3197@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:04:29 -0400 Subject: [PATCH 07/37] Add sidebar prefs, workflow & docs updates Add user-configurable sidebar and initial workflow/environment features across backend, frontend, and docs. Backend: store sidebar_config on User (JSON column), add get_sidebar_config / set_sidebar_config helpers, include sidebar_config in user.to_dict(), and validate/update sidebar_config in the update_current_user API (preset and hiddenItems validation). Frontend: refactor Sidebar to compute visible items via a shared sidebarItems module and memoized grouping/rendering; add settings UI and sidebarItems (new components) and update settings styles to support sidebar configuration. Docs & README: update README and ARCHITECTURE to document Workflow Builder, Environment Pipeline, and other new/renamed features; bump roadmap versions and reorder/expand roadmap phases to reflect Visual Designer, Automation Engine, pipeline, and monitoring work. Overall: wires up end-to-end support for customizable sidebar preferences, surfaces them via API, and updates UI + documentation to reflect new automation and pipeline features. --- README.md | 8 +- ROADMAP.md | 181 ++++---- backend/app/api/auth.py | 12 + backend/app/models/user.py | 17 + docs/ARCHITECTURE.md | 437 ++---------------- frontend/src/components/Sidebar.jsx | 247 +++------- .../components/settings/SidebarSettings.jsx | 220 +++++++++ frontend/src/components/sidebarItems.js | 210 +++++++++ frontend/src/pages/Settings.jsx | 14 +- frontend/src/styles/pages/_settings.less | 194 ++++++++ 10 files changed, 868 insertions(+), 672 deletions(-) create mode 100644 frontend/src/components/settings/SidebarSettings.jsx create mode 100644 frontend/src/components/sidebarItems.js diff --git a/README.md b/README.md index 6aec4e53..a56ea027 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,13 @@ English | [Español](docs/README.es.md) | [中文版](docs/README.zh-CN.md) | [P **Node.js** — PM2-managed applications with log streaming -**Docker** — Full container and Docker Compose management +**Workflow Builder** — Node-based visual automation for server tasks, deployments, and CI/CD -**Environment Variables** — Secure, encrypted per-app variable management +**Environment Pipeline** — Multi-environment management for WordPress (Prod/Staging/Dev) with code/DB promotion -**Git Deployment** — GitHub/GitLab webhooks, auto-deploy on push, branch selection, rollback, zero-downtime deployments +**Docker** — Full container and Docker Compose management with real-time log streaming and terminal access + +**Marketplace** — Over 60+ one-click templates for popular apps (Immich, Ghost, Authelia, etc.) ### 🏗️ Infrastructure diff --git a/ROADMAP.md b/ROADMAP.md index 0d3c7e9c..42adc19f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,15 +4,17 @@ This document outlines the development roadmap for ServerKit. Features are organ --- -## Current Version: v1.5.0 (In Development) +## Current Version: v1.6.0 (In Development) -### Recently Completed (v1.4.0) +### Recently Completed (v1.5.0) +- **New UI & Services Page** - Integrated full Services page with detail views, metrics, logs, and shell. +- **Environment Pipeline** - Multi-environment management for WordPress (Prod/Staging/Dev) with promotion/sync. +- **Visual Infrastructure Designer** - Node-based visual canvas for stack deployment and server overview. +- **Advanced Monitoring UI** - Real-time log streaming and terminal integration in the dashboard. +- **Template Library Expansion** - Over 60+ one-click deployment templates (Immich, Authelia, Ghost, etc.). - **Team & Permissions** - RBAC with admin/developer/viewer roles, invitations, audit logging -- **API Enhancements** - API keys, rate limiting, webhook subscriptions, OpenAPI docs, analytics - **SSO & OAuth Login** - Google, GitHub, OIDC, SAML with account linking -- **Database Migrations** - Flask-Migrate/Alembic with versioned schema migrations -- **Email Server Management** - Postfix, Dovecot, DKIM, SpamAssassin, Roundcube --- @@ -178,7 +180,22 @@ This document outlines the development roadmap for ServerKit. Features are organ --- -## Phase 14: Team & Permissions (Completed) +## Phase 14: Visual Infrastructure Designer (Completed) + +**Priority: High** + +The visual canvas for designing and deploying entire infrastructure stacks. + +- [x] Node-based Visual Canvas (`WorkflowBuilder.jsx`) using React Flow +- [x] Infrastructure component nodes (Docker, Database, Domain, Service) +- [x] Smart connection rules (link apps to DBs, domains to apps) +- [x] One-click stack deployment from the canvas +- [x] Template-based stack generation +- [x] Server overview mode (visualize existing infrastructure) + +--- + +## Phase 15: Team & Permissions (Completed) **Priority: Medium** @@ -191,7 +208,7 @@ This document outlines the development roadmap for ServerKit. Features are organ --- -## Phase 15: API Enhancements (Completed) +## Phase 16: API Enhancements (Completed) **Priority: Medium** @@ -203,7 +220,7 @@ This document outlines the development roadmap for ServerKit. Features are organ --- -## Phase 16: Advanced Security (Completed) +## Phase 17: Advanced Security (Completed) **Priority: High** @@ -219,7 +236,7 @@ This document outlines the development roadmap for ServerKit. Features are organ --- -## Phase 17: SSO & OAuth Login (Completed) +## Phase 18: SSO & OAuth Login (Completed) **Priority: High** @@ -236,101 +253,86 @@ This document outlines the development roadmap for ServerKit. Features are organ --- -## Phase 18: Database Migrations & Schema Versioning (Completed) +## Phase 19: Database Migrations & Schema Versioning (Completed) **Priority: High** -### Backend — Migration Engine -- [x] Integrate Flask-Migrate (Alembic) for versioned schema migrations -- [x] Generate initial migration from current model state as baseline -- [x] Replace `_auto_migrate_columns()` hack with proper Alembic migrations -- [x] Store schema version in a `schema_version` table (current version, history) -- [x] API endpoints for migration status, apply, and rollback -- [x] Auto-detect pending migrations on login and flag the session -- [x] Pre-migration automatic DB backup before applying changes -- [x] Migration scripts for all existing model changes (retroactive baseline) - -### CLI Fallback -- [x] CLI commands for headless/SSH scenarios (`flask db upgrade`, `flask db status`) -- [x] CLI rollback support (`flask db downgrade`) +- [x] Flask-Migrate (Alembic) integration +- [x] Migration wizard UI (Completed) +- [x] CLI fallback support --- -# Upcoming Development +## Phase 20: New UI & Services Page (Completed) -The phases below are ordered by priority. Higher phases ship first. +**Priority: Critical** ---- +Integrated full Services page with detail views, metrics, logs, shell, settings, and package management. -## Phase 19: New UI & Services Page (Planned) +- [x] Services list page with status indicators and quick actions +- [x] Service detail page with tabbed interface (Metrics, Logs, Shell, Settings, Commands, Events, Packages) +- [x] Git connect modal for linking services to repositories +- [x] Gunicorn management tab for Python services +- [x] Service type detection and type-specific UI (Node, Python, PHP, Docker, etc.) -**Priority: Critical** +--- -Merge the `new-ui` branch — adds a full Services page with service detail views, metrics, logs, shell, settings, git connect, and package management. +## Phase 21: Environment Pipeline (Completed) -- [ ] Merge `new-ui` branch into main development line -- [ ] Services list page with status indicators and quick actions -- [ ] Service detail page with tabbed interface (Metrics, Logs, Shell, Settings, Commands, Events, Packages) -- [ ] Git connect modal for linking services to repositories -- [ ] Gunicorn management tab for Python services -- [ ] Service type detection and type-specific UI (Node, Python, PHP, Docker, etc.) -- [ ] Resolve any conflicts with features added since branch diverged +**Priority: High** + +- [x] WordPress multi-environment pipeline (Prod/Staging/Dev) +- [x] Code and Database promotion between environments +- [x] Production syncing and environment locking --- -## Phase 20: Customizable Sidebar & Dashboard Views (Planned) +## Phase 22: Container Logs & Monitoring UI (Completed) **Priority: High** -Let users personalize what they see. Not everyone runs email servers or manages Docker — the sidebar should adapt to each user's needs. +- [x] Real-time log streaming via WebSocket with ANSI color support +- [x] Web-based terminal (`Terminal.jsx`) with shell access +- [x] Per-app resource usage charts (CPU, RAM) +- [x] Log search and filtering -- [ ] Sidebar configuration page in Settings -- [ ] Preset view profiles: **Full** (default, all modules), **Web Hosting** (apps, domains, SSL, databases, files), **Email Admin** (email, DNS, security), **Docker/DevOps** (containers, deployments, git, monitoring), **Minimal** (apps, monitoring, backups only) -- [ ] Custom view builder — toggle individual sidebar items on/off -- [ ] Per-user preference storage (saved to user profile, synced across sessions) -- [ ] Sidebar sections collapse/expand with memory -- [ ] Quick-switch between saved view profiles -- [ ] Admin can set default view for new users -- [ ] Hide empty/unconfigured modules automatically (e.g., hide Email if no email domains exist) +--- + +# Upcoming Development + +The phases below are ordered by priority. Higher phases ship first. --- -## Phase 21: Migration Wizard Frontend UI (Planned) +## Phase 23: Advanced Workflow & Automation Engine (NEW) -**Priority: High** +**Priority: Critical** -The backend migration engine is complete — this adds the visual upgrade experience (Matomo-style). +Moving beyond static design to dynamic, event-driven automation. This turns ServerKit into a powerful automation hub. -- [ ] Full-screen modal/wizard that appears when pending migrations are detected -- [ ] Step 1: "Update Available" — show current version vs new version, changelog summary -- [ ] Step 2: "Backup" — auto-backup the database, show progress, confirm success -- [ ] Step 3: "Apply Migrations" — run migrations with real-time progress/log output -- [ ] Step 4: "Done" — success confirmation with summary of changes applied -- [ ] Error handling: if a migration fails, show the error and offer rollback option -- [ ] Block access to the panel until migrations are applied -- [ ] Migration history page in Settings showing all past migrations and timestamps +- [ ] **Event Triggers:** Run workflows on Git push, health check failure, high CPU usage, or Webhook receipt. +- [ ] **Cron Integration:** Schedule workflows to run on recurring intervals (e.g., "Every Sunday at 2 AM, backup all DBs and rotate logs"). +- [ ] **Cross-Server Actions:** "When Server A's database is backed up, sync it to Server B and notify Discord." +- [ ] **Logic Nodes:** If/Else conditions, loops, and custom Python/Shell script execution nodes. +- [ ] **State Machine:** Track workflow execution history, retries on failure, and execution logs. +- [ ] **Global Variables:** Pass data between workflow steps (e.g., take the output of a build step and pass it to a deployment step). --- -## Phase 22: Container Logs & Monitoring UI (Planned) +## Phase 24: Customizable Sidebar & Dashboard Views (Completed) **Priority: High** -The container logs API is already built. This phase adds the frontend and extends monitoring to per-app metrics. +Let users personalize what they see. Not everyone runs email servers or manages Docker — the sidebar should adapt to each user's needs. -- [ ] Log viewer component with terminal-style display and ANSI color support -- [ ] Real-time log streaming via WebSocket with auto-scroll (pause on user scroll) -- [ ] Log search with regex support and match highlighting -- [ ] Filter by log level (INFO, WARN, ERROR, DEBUG) and time range -- [ ] Export filtered logs to file -- [ ] Per-container resource collection (CPU %, memory, network I/O via Docker stats API) -- [ ] Per-app resource usage charts (Recharts) with time range selector (1h, 6h, 24h, 7d) -- [ ] Per-app alert rules (metric, operator, threshold, duration) -- [ ] Alert notifications via existing channels (email, Discord, Telegram) with cooldown +- [x] Sidebar configuration page in Settings +- [x] Preset view profiles (Full, Web Hosting, Email Admin, Docker/DevOps, Minimal) +- [x] Custom view builder — toggle individual sidebar items on/off +- [x] Per-user preference storage (saved to user profile) --- -## Phase 23: Agent Fleet Management (Planned) +## Phase 25: Agent Fleet Management (Planned) **Priority: High** @@ -350,7 +352,7 @@ Level up agent management from "connect and monitor" to full fleet control. --- -## Phase 24: Cross-Server Monitoring Dashboard (Planned) +## Phase 26: Cross-Server Monitoring Dashboard (Planned) **Priority: High** @@ -369,7 +371,7 @@ Fleet-wide visibility — see everything at a glance and catch problems early. --- -## Phase 25: Agent Plugin System (Planned) +## Phase 27: Agent Plugin System (Planned) **Priority: High** @@ -389,22 +391,9 @@ Make the agent extensible — let users add custom capabilities without modifyin - [ ] Scheduled tasks — plugins can register periodic jobs (cron-like) - [ ] Event hooks — plugins can react to agent events (connect, disconnect, command, alert) -### Panel Integration -- [ ] Plugin management UI — install, configure, monitor plugins per server -- [ ] Plugin marketplace / registry — browse and install community plugins -- [ ] Plugin configuration editor — per-server plugin settings from the panel -- [ ] Plugin logs and diagnostics — view plugin output and errors -- [ ] Plugin metrics visualization — custom widgets for plugin-reported data - -### Developer Experience -- [ ] Plugin SDK (Go module) — scaffolding, helpers, testing tools -- [ ] Plugin template repository — quickstart for new plugin development -- [ ] Local plugin development mode — hot-reload, debug logging -- [ ] Plugin documentation and API reference - --- -## Phase 26: Server Templates & Config Sync (Planned) +## Phase 28: Server Templates & Config Sync (Planned) **Priority: Medium** @@ -423,7 +412,7 @@ Define what a server should look like, apply it, and detect when it drifts. --- -## Phase 27: Multi-Tenancy & Workspaces (Planned) +## Phase 29: Multi-Tenancy & Workspaces (Planned) **Priority: Medium** @@ -442,7 +431,7 @@ Isolate servers by team, client, or project. Essential for agencies, MSPs, and l --- -## Phase 28: Advanced SSL Features (Planned) +## Phase 30: Advanced SSL Features (Planned) **Priority: Medium** @@ -456,7 +445,7 @@ Isolate servers by team, client, or project. Essential for agencies, MSPs, and l --- -## Phase 29: DNS Zone Management (Planned) +## Phase 31: DNS Zone Management (Planned) **Priority: Medium** @@ -473,7 +462,7 @@ Full DNS record management with provider API integration. --- -## Phase 30: Nginx Advanced Configuration (Planned) +## Phase 32: Nginx Advanced Configuration (Planned) **Priority: Medium** @@ -491,7 +480,7 @@ Go beyond basic virtual hosts — full reverse proxy and performance configurati --- -## Phase 31: Status Page & Health Checks (Planned) +## Phase 33: Status Page & Health Checks (Planned) **Priority: Medium** @@ -510,7 +499,7 @@ Public-facing status page and automated health monitoring. --- -## Phase 32: Server Provisioning APIs (Planned) +## Phase 34: Server Provisioning APIs (Planned) **Priority: Medium** @@ -529,7 +518,7 @@ Spin up and manage cloud servers directly from the panel. --- -## Phase 33: Performance Optimization (Planned) +## Phase 35: Performance Optimization (Planned) **Priority: Low** @@ -542,7 +531,7 @@ Spin up and manage cloud servers directly from the panel. --- -## Phase 34: Mobile App (Future) +## Phase 36: Mobile App (Future) **Priority: Low — v3.0+** @@ -554,7 +543,7 @@ Spin up and manage cloud servers directly from the panel. --- -## Phase 35: Marketplace & Extensions (Future) +## Phase 37: Marketplace & Extensions (Future) **Priority: Low — v3.0+** @@ -576,8 +565,8 @@ Spin up and manage cloud servers directly from the panel. | v1.2.0 | Backups, Advanced SSL, Advanced Security | Completed | | v1.3.0 | Email server, API enhancements | Completed | | v1.4.0 | Team & permissions, SSO & OAuth login | Completed | -| v1.5.0 | New UI, customizable sidebar, migration wizard UI | Current | -| v1.6.0 | Container monitoring UI, agent fleet management | Planned | +| v1.5.0 | New UI, Visual Designer, Services Page | Current | +| v1.6.0 | Advanced Automation Engine, fleet management | Planned | | v1.7.0 | Cross-server monitoring, agent plugin system | Planned | | v1.8.0 | Server templates, multi-tenancy | Planned | | v1.9.0 | Advanced SSL, DNS management, Nginx config | Planned | diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index adc7d7c7..471b0131 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -312,6 +312,18 @@ def update_current_user(): return jsonify({'error': 'Password must be at least 8 characters'}), 400 user.set_password(data['password']) + if 'sidebar_config' in data: + config = data['sidebar_config'] + if isinstance(config, dict): + preset = config.get('preset', 'full') + valid_presets = ['full', 'web', 'email', 'devops', 'minimal', 'custom'] + if preset not in valid_presets: + return jsonify({'error': f'Invalid sidebar preset: {preset}'}), 400 + hidden = config.get('hiddenItems', []) + if not isinstance(hidden, list): + return jsonify({'error': 'hiddenItems must be a list'}), 400 + user.set_sidebar_config({'preset': preset, 'hiddenItems': hidden}) + db.session.commit() return jsonify({'user': user.to_dict()}), 200 diff --git a/backend/app/models/user.py b/backend/app/models/user.py index ab8fb244..8c373d87 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -36,6 +36,9 @@ class User(db.Model): backup_codes = db.Column(db.Text, nullable=True) # JSON array of hashed backup codes totp_confirmed_at = db.Column(db.DateTime, nullable=True) # When 2FA was enabled + # Sidebar preferences: { preset: 'full'|'web'|'email'|'devops'|'minimal'|'custom', hiddenItems: [...] } + sidebar_config = db.Column(db.Text, nullable=True) + # Relationships applications = db.relationship('Application', backref='owner', lazy='dynamic') @@ -166,6 +169,19 @@ def has_permission(self, feature, level='read'): feature_perms = perms.get(feature, {}) return feature_perms.get(level, False) + def get_sidebar_config(self): + """Return sidebar config dict, or default.""" + if self.sidebar_config: + try: + return json.loads(self.sidebar_config) + except (json.JSONDecodeError, TypeError): + pass + return {'preset': 'full', 'hiddenItems': []} + + def set_sidebar_config(self, config): + """Store sidebar config as JSON.""" + self.sidebar_config = json.dumps(config) if config else None + def to_dict(self): return { 'id': self.id, @@ -177,6 +193,7 @@ def to_dict(self): 'totp_enabled': self.totp_enabled, 'auth_provider': self.auth_provider or 'local', 'has_password': self.has_password, + 'sidebar_config': self.get_sidebar_config(), 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat(), 'last_login_at': self.last_login_at.isoformat() if self.last_login_at else None, diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9b2aad3a..eb52d3ff 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -11,6 +11,8 @@ - [Template System](#template-system) - [Port Allocation](#port-allocation) - [Database Linking](#database-linking) +- [Workflow Automation](#workflow-automation) +- [Environment Pipeline](#environment-pipeline) - [File Paths](#file-paths) --- @@ -97,27 +99,6 @@ User Request What Happens └─────────┘ ``` -### Detailed Nginx → Container Flow - -``` - NGINX CONFIG DOCKER - /etc/nginx/sites-enabled/ CONTAINER - ───────────────────────── ───────────────── - - server { - listen 80; - server_name my-blog.com; - ┌─────────────────┐ - location / { │ WordPress │ - proxy_pass ─────────────────────────► │ │ - http://127.0.0.1:8001; │ 0.0.0.0:80 │ - proxy_set_header Host $host; │ ▲ │ - proxy_set_header X-Real-IP ...; │ │ │ - } │ (mapped to │ - } │ host:8001) │ - └─────────────────┘ -``` - --- ## Template System @@ -135,106 +116,6 @@ User Request What Happens │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ └────────┼────────────┼────────────┼────────────┼────────────┼───────────────────┘ - │ │ │ │ │ - │ User clicks "Deploy" in UI │ - │ │ │ - ▼ ▼ ▼ -┌─────────────────────────────────────────────────────────────────────────────────┐ -│ SERVERKIT BACKEND │ -│ │ -│ TemplateService.install_template() │ -│ ├── 1. Parse template YAML │ -│ ├── 2. Generate unique port (8000-60000) │ -│ ├── 3. Substitute variables: │ -│ │ ${APP_NAME} → "my-blog" │ -│ │ ${HTTP_PORT} → "8247" │ -│ │ ${DB_PASSWORD} → "auto_generated" │ -│ ├── 4. Create /var/serverkit/apps/my-blog/docker-compose.yml │ -│ ├── 5. Run: docker compose up -d --build │ -│ └── 6. Store app record in database │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────────────────────────┐ -│ APP CREATED │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ App Name: my-blog │ │ -│ │ Type: docker │ │ -│ │ Port: 8247 (auto-assigned) │ │ -│ │ Status: running │ │ -│ │ Container: my-blog │ │ -│ │ Path: /var/serverkit/apps/my-blog/ │ │ -│ │ │ │ -│ │ Private URL: http://server-ip:8247 ◄── Works immediately! │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────┘ - │ - │ User clicks "Connect Domain" - ▼ -┌─────────────────────────────────────────────────────────────────────────────────┐ -│ DOMAIN CONNECTED │ -│ │ -│ DomainService.create_domain() │ -│ ├── 1. Validate domain DNS points to server │ -│ ├── 2. Check container port is accessible │ -│ ├── 3. Generate Nginx config: │ -│ │ │ -│ │ server { │ -│ │ listen 80; │ -│ │ server_name my-blog.com; │ -│ │ │ -│ │ location / { │ -│ │ proxy_pass http://127.0.0.1:8247; ◄── Container port │ -│ │ 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; │ -│ │ } │ -│ │ } │ -│ │ │ -│ ├── 4. Write to /etc/nginx/sites-available/my-blog │ -│ ├── 5. Symlink to /etc/nginx/sites-enabled/my-blog │ -│ ├── 6. Test config: nginx -t │ -│ ├── 7. Reload: systemctl reload nginx │ -│ └── 8. (Optional) Request SSL via Let's Encrypt │ -│ │ -│ Public URL: https://my-blog.com ◄── Now accessible worldwide! │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────┘ -``` - -### Template YAML Structure - -```yaml -# Example: flask-hello-world.yaml - -id: flask-hello-world -name: Flask - Hello World -version: "1.0" -description: Simple Flask debug app -categories: - - development - - api - -variables: - - name: HTTP_PORT # Variable name - type: port # Auto-generates available port - default: "5000" # Starting port to search from - hidden: true # Don't show in UI - -compose: # Docker Compose configuration - services: - app: - image: python:3.12-slim - container_name: ${APP_NAME} - ports: - - "${HTTP_PORT}:5000" # Host:Container port mapping - environment: - - APP_NAME=${APP_NAME} - - EXTERNAL_PORT=${HTTP_PORT} ``` --- @@ -243,144 +124,62 @@ compose: # Docker Compose configuration ### How ServerKit Finds Available Ports +ServerKit automatically scans the database, Docker, and system sockets to find the first available port (starting from 8000) for new applications, ensuring no conflicts occur during deployment. + +--- + +## Database Linking + +### How Apps Connect to Databases + +ServerKit automates the creation of databases and users, then injects the credentials as environment variables (`DB_HOST`, `DB_USER`, etc.) directly into the application container, allowing for seamless connectivity. + +--- + +## Workflow Automation + +ServerKit includes a node-based visual workflow builder for automating server tasks. + ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ -│ PORT ALLOCATION │ +│ WORKFLOW BUILDER │ │ │ -│ TemplateService._find_available_port(start=8000) │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ Step 1: Check Database │ │ -│ │ SELECT port FROM applications WHERE port IS NOT NULL │ │ -│ │ Result: [8001, 8002, 8005, 8010] │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ Step 2: Check Docker │ │ -│ │ docker ps --format '{{.Ports}}' │ │ -│ │ Parse: "0.0.0.0:8003->80/tcp" → 8003 │ │ -│ │ Result: [8003, 8004] │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ Step 3: Socket Bind Test │ │ -│ │ Try: socket.bind(('127.0.0.1', port)) │ │ -│ │ If fails → port in use by system │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ Step 4: Return First Available │ │ -│ │ │ │ -│ │ Checking 8000... taken (DB) │ │ -│ │ Checking 8001... taken (DB) │ │ -│ │ Checking 8002... taken (DB) │ │ -│ │ Checking 8003... taken (Docker) │ │ -│ │ Checking 8004... taken (Docker) │ │ -│ │ Checking 8005... taken (DB) │ │ -│ │ Checking 8006... AVAILABLE ✓ │ │ -│ │ │ │ -│ │ Return: 8006 │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ +│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ +│ │ TRIGGER │──────▶│ ACTION │──────▶│ CONDITION │──────▶│ NOTIFY │ │ +│ │ (Git Push)│ │ (Build) │ │ (Success?)│ │ (Discord) │ │ +│ └───────────┘ └───────────┘ └─────┬─────┘ └───────────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────┐ │ +│ │ ROLLBACK │ │ +│ └───────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────┘ ``` -### Port Map Example - -``` -┌──────────┬────────────────────────────────────────┐ -│ Port │ Service │ -├──────────┼────────────────────────────────────────┤ -│ 22 │ SSH │ -│ 80 │ Nginx (HTTP) │ -│ 443 │ Nginx (HTTPS) │ -│ 3306 │ MySQL │ -│ 5432 │ PostgreSQL │ -│ 5000 │ ServerKit Backend API │ -│ 6379 │ Redis │ -├──────────┼────────────────────────────────────────┤ -│ 8001 │ App: wordpress-blog │ -│ 8002 │ App: flask-api │ -│ 8003 │ App: node-frontend │ -│ 8004 │ App: grafana-monitoring │ -│ 8005 │ App: n8n-automation │ -│ 8006 │ App: (next available) │ -│ ... │ ... │ -│ 60000 │ (max port range) │ -└──────────┴────────────────────────────────────────┘ -``` +- **Nodes:** Represent individual steps (Triggers, Actions, Logic, Notifications). +- **Edges:** Define the flow of execution. +- **Engine:** The `WorkflowService` parses the JSON graph and executes steps sequentially or in parallel. --- -## Database Linking +## Environment Pipeline -### How Apps Connect to Databases +Specifically designed for WordPress, the environment pipeline allows for professional staging/dev workflows. ``` -┌────────────────────────────────────────────────────────────────────────────────┐ -│ │ -│ ┌──────────────────┐ ┌──────────────────┐ │ -│ │ APP (Flask) │ │ DATABASE (MySQL) │ │ -│ │ │ │ │ │ -│ │ Needs DB access │ │ db: my_app_db │ │ -│ │ │ │ user: app_user │ │ -│ └────────┬─────────┘ └────────┬─────────┘ │ -│ │ │ │ -│ │ User clicks "Link Database" │ │ -│ │ │ │ -│ └──────────────┬───────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────────────────┐ │ -│ │ SERVERKIT LINKS THEM │ │ -│ │ │ │ -│ │ 1. Creates database: my_app_db │ │ -│ │ 2. Creates user with secure password │ │ -│ │ 3. Grants permissions │ │ -│ │ 4. Injects environment variables into app container: │ │ -│ │ │ │ -│ │ ┌─────────────────────────────────────────────────────────────┐ │ │ -│ │ │ DB_HOST=localhost │ │ │ -│ │ │ DB_PORT=3306 │ │ │ -│ │ │ DB_NAME=my_app_db │ │ │ -│ │ │ DB_USER=app_user │ │ │ -│ │ │ DB_PASSWORD=xK9#mP2$vL7@nQ4 │ │ │ -│ │ │ │ │ │ -│ │ │ # Also provides connection URL format: │ │ │ -│ │ │ DATABASE_URL=mysql://app_user:xK9#mP2$vL7@nQ4@localhost/db │ │ │ -│ │ └─────────────────────────────────────────────────────────────┘ │ │ -│ │ │ │ -│ │ 5. Restarts app container to pick up new env vars │ │ -│ │ │ │ -│ └────────────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────────────────┐ │ -│ │ APP CODE │ │ -│ │ │ │ -│ │ # Python/Flask example │ │ -│ │ import os │ │ -│ │ import mysql.connector │ │ -│ │ │ │ -│ │ db = mysql.connector.connect( │ │ -│ │ host=os.environ['DB_HOST'], # localhost │ │ -│ │ port=os.environ['DB_PORT'], # 3306 │ │ -│ │ database=os.environ['DB_NAME'], # my_app_db │ │ -│ │ user=os.environ['DB_USER'], # app_user │ │ -│ │ password=os.environ['DB_PASSWORD'] │ │ -│ │ ) │ │ -│ │ │ │ -│ │ # Or use the URL directly: │ │ -│ │ # SQLAlchemy: create_engine(os.environ['DATABASE_URL']) │ │ -│ │ │ │ -│ └────────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────┘ +┌──────────────┐ promotion ┌──────────────┐ promotion ┌──────────────┐ +│ DEV │───────────▶│ STAGING │───────────▶│ PRODUCTION │ +│ (Standalone) │ │ (Standalone) │ │ (Production) │ +└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ + │ │ │ + └─────────── sync ──────────┴─────────── sync ──────────┘ ``` +- **Promotion:** Push code (Git) and Database from a lower environment to a higher one. +- **Syncing:** Pull the latest production database and media to dev/staging for testing. +- **Sanitization:** Automatically strip sensitive user data during sync. + --- ## File Paths @@ -388,168 +187,30 @@ compose: # Docker Compose configuration ### Where Everything Lives ``` -SERVER FILESYSTEM -───────────────────────────────────────────────────────────────────────────────── - /var/serverkit/ # ServerKit data root ├── apps/ # All deployed applications -│ ├── my-blog/ -│ │ ├── docker-compose.yml # Generated from template -│ │ ├── .env # Environment variables -│ │ └── data/ # Persistent volumes -│ ├── flask-api/ -│ │ ├── docker-compose.yml -│ │ └── app/ # Application code -│ └── ... -│ ├── backups/ # Database backups -│ ├── mysql/ -│ └── postgres/ -│ -└── ssl/ # SSL certificates (if not using certbot) +└── ssl/ # SSL certificates /etc/serverkit/ # ServerKit configuration ├── templates/ # Template library (YAML files) -│ ├── wordpress.yaml -│ ├── flask-hello-world.yaml -│ ├── grafana.yaml -│ └── ... └── config.yaml # Main config - -/etc/nginx/ # Nginx configuration -├── sites-available/ # All site configs -│ ├── my-blog # Generated by ServerKit -│ ├── flask-api -│ └── default -├── sites-enabled/ # Enabled sites (symlinks) -│ ├── my-blog -> ../sites-available/my-blog -│ └── flask-api -> ../sites-available/flask-api -└── nginx.conf # Main nginx config - -/var/log/nginx/ # Nginx logs (per-app) -├── my-blog.access.log -├── my-blog.error.log -├── flask-api.access.log -└── flask-api.error.log - -/var/lib/mysql/ # MySQL data -/var/lib/postgresql/ # PostgreSQL data ``` --- ## Component Diagram -``` -┌─────────────────────────────────────────────────────────────────────────────────┐ -│ SERVERKIT │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ FRONTEND (React) │ │ -│ │ Served via Nginx :80/443 │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │Dashboard │ │ Apps │ │ Domains │ │ Docker │ │ Security │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │Databases │ │Templates │ │ Firewall │ │ Cron │ │ Settings │ │ │ -│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ -│ │ │ │ -│ └─────────────────────────────────┬────────────────────────────────────────┘ │ -│ │ │ -│ REST API + WebSocket │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ BACKEND (Flask) │ │ -│ │ Port 5000 │ │ -│ │ │ │ -│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ -│ │ │ SERVICES │ │ │ -│ │ │ │ │ │ -│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ -│ │ │ │DockerService │ │ NginxService │ │TemplateServ. │ │ │ │ -│ │ │ │ │ │ │ │ │ │ │ │ -│ │ │ │ • compose_up │ │ • create_site│ │ • install │ │ │ │ -│ │ │ │ • logs │ │ • enable_ssl │ │ • variables │ │ │ │ -│ │ │ │ • stats │ │ • reload │ │ • validate │ │ │ │ -│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ -│ │ │ │ │ │ -│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ -│ │ │ │ DBService │ │ SSLService │ │SecurityServ. │ │ │ │ -│ │ │ │ │ │ │ │ │ │ │ │ -│ │ │ │ • create_db │ │ • certbot │ │ • ClamAV │ │ │ │ -│ │ │ │ • users │ │ • renew │ │ • 2FA │ │ │ │ -│ │ │ │ • backup │ │ • wildcard │ │ • firewall │ │ │ │ -│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ -│ │ │ │ │ │ -│ │ └──────────────────────────────────────────────────────────────────┘ │ │ -│ │ │ │ -│ └─────────────────────────────────┬────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ DATABASE (SQLite/PostgreSQL) │ │ -│ │ │ │ -│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌─────────┐ │ │ -│ │ │ Apps │ │ Domains │ │ Users │ │ Databases │ │ Settings│ │ │ -│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ └─────────┘ │ │ -│ │ │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────────────────────────────┘ -``` +ServerKit follows a modern 3-tier architecture: +1. **Frontend:** React-based dashboard served via Nginx. +2. **Backend:** Flask REST API managing Docker, Nginx, and system services. +3. **Agent:** Go-based remote agent for multi-server management. --- ## Troubleshooting -### 502 Bad Gateway - -``` -Problem: Nginx can't reach the container - -Check: -┌─────────────────────────────────────────────────────────────────┐ -│ 1. Is container running? │ -│ docker ps | grep │ -│ │ -│ 2. Is port bound to host? │ -│ docker port │ -│ Expected: 5000/tcp -> 0.0.0.0:8001 │ -│ │ -│ 3. Is port accessible? │ -│ curl -I http://127.0.0.1:8001 │ -│ Expected: HTTP/1.1 200 OK │ -│ │ -│ 4. Does nginx config have correct port? │ -│ cat /etc/nginx/sites-enabled/ │ -│ Check: proxy_pass http://127.0.0.1:8001; │ -│ │ -│ 5. Check nginx error log: │ -│ tail -50 /var/log/nginx/.error.log │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Container Won't Start - -``` -Problem: docker compose up fails - -Check: -┌─────────────────────────────────────────────────────────────────┐ -│ 1. Check compose logs: │ -│ cd /var/serverkit/apps/ │ -│ docker compose logs │ -│ │ -│ 2. Validate compose file: │ -│ docker compose config │ -│ │ -│ 3. Check for port conflicts: │ -│ docker ps --format "{{.Ports}}" │ -│ netstat -tulpn | grep │ -└─────────────────────────────────────────────────────────────────┘ -``` +Refer to the [Deployment Guide](DEPLOYMENT.md) for detailed troubleshooting steps regarding 502 errors, container failures, and networking issues. --- diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 8fb567af..199b7653 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,10 +1,11 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; import { NavLink, useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { useTheme } from '../contexts/ThemeContext'; import { Star, Settings, LogOut, Sun, Moon, Monitor, ChevronRight, ChevronUp, Layers } from 'lucide-react'; import { api } from '../services/api'; import ServerKitLogo from './ServerKitLogo'; +import { SIDEBAR_CATEGORIES, CATEGORY_LABELS, getVisibleItems } from './sidebarItems'; const Sidebar = () => { const { user, logout } = useAuth(); @@ -47,8 +48,6 @@ const Sidebar = () => { }; const scheduleNext = () => { - // Each time it plays, the next interval gets longer - // 1st: 8-11 min, 2nd: 16-22 min, 3rd: 24-33 min, etc. const multiplier = playCount + 1; const minMinutes = 8 * multiplier; const maxMinutes = 11 * multiplier; @@ -60,7 +59,6 @@ const Sidebar = () => { }, delay); }; - // First play after 1 minute const initialDelay = setTimeout(() => { triggerAnimation(); scheduleNext(); @@ -72,6 +70,55 @@ const Sidebar = () => { }; }, [whiteLabel.enabled]); + const conditions = { wpInstalled }; + + const visibleItems = useMemo( + () => getVisibleItems(user?.sidebar_config), + [user?.sidebar_config] + ); + + // Group visible items by category + const groupedItems = useMemo(() => { + const groups = {}; + for (const cat of SIDEBAR_CATEGORIES) { + const items = visibleItems.filter(item => item.category === cat); + if (items.length > 0) { + groups[cat] = items; + } + } + return groups; + }, [visibleItems]); + + const renderNavItem = (item) => ( + + `nav-item ${isActive ? 'active' : ''}`} + end={item.end || false} + > + + {item.label} + + {item.subItems?.map(sub => { + if (sub.requiresCondition && !conditions[sub.requiresCondition]) return null; + return ( + `nav-item nav-sub-item ${isActive ? 'active' : ''}`} + > + + {sub.label} + + ); + })} + + ); + return (
+
+ ); +}; + +export default WorkflowExecutionHistory; diff --git a/frontend/src/components/workflow/nodes/LogicIfNode.jsx b/frontend/src/components/workflow/nodes/LogicIfNode.jsx new file mode 100644 index 00000000..27a5354f --- /dev/null +++ b/frontend/src/components/workflow/nodes/LogicIfNode.jsx @@ -0,0 +1,54 @@ +import React, { memo } from 'react'; +import { Handle, Position } from '@xyflow/react'; +import { GitBranch, Check, X } from 'lucide-react'; + +const LogicIfNode = ({ data, selected }) => { + const { + condition = '', + label = 'If/Else' + } = data; + + return ( +
+ + +
+ +
+
+
{label}
+
+ {condition || 'No condition'} +
+
+ +
+
+ TRUE + +
+
+ FALSE + +
+
+
+ ); +}; + +export default memo(LogicIfNode); diff --git a/frontend/src/components/workflow/nodes/NotificationNode.jsx b/frontend/src/components/workflow/nodes/NotificationNode.jsx new file mode 100644 index 00000000..452ac96c --- /dev/null +++ b/frontend/src/components/workflow/nodes/NotificationNode.jsx @@ -0,0 +1,50 @@ +import React, { memo } from 'react'; +import { Handle, Position } from '@xyflow/react'; +import { Bell, MessageSquare, Mail, Slack } from 'lucide-react'; + +const NotificationNode = ({ data, selected }) => { + const { + channel = 'discord', + label = 'Notify', + message = '' + } = data; + + const getIcon = () => { + switch (channel) { + case 'discord': return ; + case 'slack': return ; + case 'email': return ; + default: return ; + } + }; + + return ( +
+ + +
+ {getIcon()} +
+
+
{label}
+
+ {channel.charAt(0).toUpperCase() + channel.slice(1)} +
+
+ + +
+ ); +}; + +export default memo(NotificationNode); diff --git a/frontend/src/components/workflow/nodes/ScriptNode.jsx b/frontend/src/components/workflow/nodes/ScriptNode.jsx new file mode 100644 index 00000000..896b4357 --- /dev/null +++ b/frontend/src/components/workflow/nodes/ScriptNode.jsx @@ -0,0 +1,49 @@ +import React, { memo } from 'react'; +import { Handle, Position } from '@xyflow/react'; +import { Terminal, Code, Cpu } from 'lucide-react'; + +const ScriptNode = ({ data, selected }) => { + const { + language = 'bash', + label = 'Run Script', + content = '' + } = data; + + const getIcon = () => { + switch (language) { + case 'bash': return ; + case 'python': return ; + default: return ; + } + }; + + return ( +
+ + +
+ {getIcon()} +
+
+
{label}
+
+ {language.charAt(0).toUpperCase() + language.slice(1)} +
+
+ + +
+ ); +}; + +export default memo(ScriptNode); diff --git a/frontend/src/components/workflow/nodes/TriggerNode.jsx b/frontend/src/components/workflow/nodes/TriggerNode.jsx new file mode 100644 index 00000000..5247ca64 --- /dev/null +++ b/frontend/src/components/workflow/nodes/TriggerNode.jsx @@ -0,0 +1,54 @@ +import React, { memo } from 'react'; +import { Handle, Position } from '@xyflow/react'; +import { Zap, Clock, Webhook, Activity, Play } from 'lucide-react'; + +const TriggerNode = ({ data, selected }) => { + const { + triggerType = 'manual', + label = 'Trigger', + isActive = false, + lastRunAt = null, + lastStatus = null + } = data; + + const getIcon = () => { + switch (triggerType) { + case 'manual': return ; + case 'cron': return ; + case 'webhook': return ; + case 'event': return ; + default: return ; + } + }; + + const getStatusColor = () => { + if (!isActive) return 'border-gray-600'; + if (lastStatus === 'success') return 'border-green-500 shadow-[0_0_10px_rgba(34,197,94,0.3)]'; + if (lastStatus === 'failed') return 'border-red-500 shadow-[0_0_10px_rgba(239,68,68,0.3)]'; + return 'border-blue-500 shadow-[0_0_10px_rgba(59,130,246,0.3)]'; + }; + + return ( +
+
+ {getIcon()} +
+
+
{label}
+
+ {triggerType.charAt(0).toUpperCase() + triggerType.slice(1)} + {isActive ? ' • Active' : ' • Disabled'} +
+
+ + +
+ ); +}; + +export default memo(TriggerNode); diff --git a/frontend/src/components/workflow/panels/LogicIfConfigPanel.jsx b/frontend/src/components/workflow/panels/LogicIfConfigPanel.jsx new file mode 100644 index 00000000..fe7525ed --- /dev/null +++ b/frontend/src/components/workflow/panels/LogicIfConfigPanel.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { X, GitBranch } from 'lucide-react'; + +const LogicIfConfigPanel = ({ node, onChange, onClose }) => { + const { data } = node; + const { condition = '', label = 'If/Else' } = data; + + return ( +
+
+

Logic Configuration

+ +
+ +
+
+ + onChange({ ...data, label: e.target.value })} + /> +
+ +
+ + onChange({ ...data, condition: e.target.value })} + placeholder="e.g. results['node_1']['returncode'] == 0" + /> +
+ +
+

+ Available Variables:
+ results: Dictionary of previous node outputs
+ context: Trigger execution context +

+
+ +
+
+
+ True branch connects to "TRUE" handle +
+
+
+ False branch connects to "FALSE" handle +
+
+
+
+ ); +}; + +export default LogicIfConfigPanel; diff --git a/frontend/src/components/workflow/panels/NotificationConfigPanel.jsx b/frontend/src/components/workflow/panels/NotificationConfigPanel.jsx new file mode 100644 index 00000000..04e7ccfc --- /dev/null +++ b/frontend/src/components/workflow/panels/NotificationConfigPanel.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { X, MessageSquare, Mail, Slack, Bell } from 'lucide-react'; + +const NotificationConfigPanel = ({ node, onChange, onClose }) => { + const { data } = node; + const { channel = 'discord', label = 'Notify', message = '' } = data; + + return ( +
+
+

Notification Configuration

+ +
+ +
+
+ + onChange({ ...data, label: e.target.value })} + /> +
+ +
+ +
+ + + + +
+
+ +
+ +