Files
ServerKit/install.sh
T
Juan Denis 6c41e88701 Add auth checks, logging, DNS API & UI
Enforce admin and workspace permissions, improve logging, and add DNS frontend/API support.

- Backend: added admin checks to cloud_provisioning and marketplace endpoints; added require_workspace_access helper and applied to multiple workspace endpoints to enforce membership/admin access; persist push subscription data in mobile API (commit to DB); replaced print() debugging with structured logger calls across agent_registry and added logging/exception handling in monitoring_service.
- Frontend: added a DNS API client (frontend/src/services/api/dns.js) and registered it in the API index; extended DNSZones UI to collect provider zone ID and API token, updated create payload and form reset; added optional modal footer prop to Modal component; moved rollup native binding to optionalDependencies in package.json.
- Misc: added '*.png' to .gitignore and a minor spacing change in install.sh.

These changes improve security checks, observability, and add DNS management features in the UI/API.
2026-03-24 06:10:58 -04:00

462 lines
14 KiB
Bash

#!/bin/bash
#
# 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'
BLUE='\033[0;34m'
NC='\033[0m'
INSTALL_DIR="/opt/serverkit"
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=""
SAFE_MODE=false
print_header() {
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 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
print_warning "System Python not in supported range ($PYTHON_MIN-$PYTHON_MAX)"
}
# 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
}
# 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
fi
# 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"
else
print_warning "Unsupported OS ($ID). This script is designed for Ubuntu/Debian/Fedora."
fi
else
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
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 \
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"
else
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"
else
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"
else
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
else
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..."
$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 --no-cache-dir -r "$INSTALL_DIR/backend/requirements.txt"
else
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)
cat > "$INSTALL_DIR/.env" << EOF
# ServerKit Configuration
# Generated on $(date)
# 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
# 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..."
if [ "$OS_FAMILY" = "fedora" ]; then
dnf install -y nginx
else
apt-get install -y nginx
fi
# Stop nginx and remove default site
systemctl stop nginx 2>/dev/null || true
rm -f /etc/nginx/sites-enabled/default
# Ensure sites directories exist
mkdir -p /etc/nginx/sites-available
mkdir -p /etc/nginx/sites-enabled
# 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
# 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
# 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
# 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"
else
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"
else
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 --project-directory $INSTALL_DIR [up|down]"
echo ""
echo "For all commands: serverkit help"
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 --project-directory $INSTALL_DIR logs -f"
echo ""
fi