mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-30 08:36:55 +09:00
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.
462 lines
14 KiB
Bash
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
|