#!/usr/bin/env bash set -euo pipefail # ────────────────────────────────────────────── # worst-scan-web — Direct installer (no Docker) # Usage: # screen -S worst-scan # recommended — survives SSH drops # bash <(curl -fsSL https://gitea.cbcren.online/renato97/worst-scan-web/raw/branch/main/install.sh) # ────────────────────────────────────────────── REPO_URL="https://gitea.cbcren.online/renato97/worst-scan-web.git" INSTALL_DIR="${HOME}/worst-scan-web" BRANCH="main" RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' log() { printf "${GREEN}✓${NC} %s\n" "$1"; } warn() { printf "${YELLOW}⚠${NC} %s\n" "$1"; } err() { printf "${RED}✗${NC} %s\n" "$1"; } info() { printf "${CYAN}→${NC} %s\n" "$1"; } # ── OS detection ──────────────────────────────── detect_os() { if [ -f /etc/os-release ]; then . /etc/os-release && echo "$ID" elif command -v lsb_release &>/dev/null; then lsb_release -si | tr '[:upper:]' '[:lower:]' else echo "unknown"; fi } OS=$(detect_os) bail() { err "Unsupported OS: $OS. Install manually: $1"; exit 1; } # ── Sudo check ───────────────────────────────── sudo -v || (err "sudo required" && exit 1) SU() { echo "$SUDO_PASS" | sudo -S "$@" 2>/dev/null || sudo "$@"; } # ── Swap (prevent OOM on low-RAM VPS) ────────── if [ "$(free -m | awk '/^Swap:/{print $2}')" -lt 512 ]; then info "Creating 1GB swap file..." SU dd if=/dev/zero of=/swapfile bs=1M count=1024 status=none SU chmod 600 /swapfile SU mkswap /swapfile >/dev/null SU swapon /swapfile if ! grep -q /swapfile /etc/fstab 2>/dev/null; then echo '/swapfile none swap sw 0 0' | SU tee -a /etc/fstab >/dev/null fi log "Swap created (1GB)" fi # ── git + curl ───────────────────────────────── for cmd in git curl; do if ! command -v "$cmd" &>/dev/null; then info "Installing $cmd..." case "$OS" in ubuntu|debian) sudo apt update -qq && sudo apt install -y -qq "$cmd" ;; arch|endeavouros) sudo pacman -S --noconfirm "$cmd" ;; fedora|centos|rhel|*) sudo dnf install -y "$cmd" ;; esac log "$cmd installed" fi done # ── Docker cleanup ────────────────────────────── if command -v docker &>/dev/null; then info "Cleaning up Docker..." if [ -f "$INSTALL_DIR/docker-compose.yml" ]; then (cd "$INSTALL_DIR" && docker compose down) 2>/dev/null || true fi sudo docker stop worst-scan-web 2>/dev/null || true sudo docker rm worst-scan-web 2>/dev/null || true sudo systemctl stop docker docker.socket 2>/dev/null || true sudo systemctl disable docker docker.socket 2>/dev/null || true sudo apt remove -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras 2>/dev/null || true sudo rm -f /etc/apt/sources.list.d/docker.list /etc/apt/keyrings/docker.asc sudo apt update -qq 2>/dev/null || true log "Docker removed" fi # ── Node.js 22 ────────────────────────────────── if ! command -v node &>/dev/null || [ "$(node -v | cut -d. -f1 | tr -d v)" -lt 22 ]; then info "Installing Node.js 22..." case "$OS" in ubuntu|debian) curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash sudo apt install -y -qq nodejs ;; arch|endeavouros) sudo pacman -S --noconfirm nodejs npm ;; fedora) curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash sudo dnf install -y nodejs ;; *) bail "https://nodejs.org/en/download/" ;; esac log "Node.js $(node -v) installed" else log "Node.js $(node -v) already installed" fi # ── npm mirror (auto-detect China) ────────────── if curl -sI --connect-timeout 3 https://registry.npmjs.org/ 2>&1 | grep -q "timed out\|Connection refused\|Could not resolve\|Network"; then info "npmjs.org unreachable — switching to China mirror" npm config set registry https://registry.npmmirror.com log "Using npmmirror.com" fi # ── Caddy ──────────────────────────────────────── if ! command -v caddy &>/dev/null; then info "Installing Caddy..." case "$OS" in ubuntu|debian) sudo apt install -y -qq debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/setup.deb.sh' | sudo bash sudo apt install -y -qq caddy ;; arch|endeavouros) sudo pacman -S --noconfirm caddy ;; fedora) sudo dnf install -y 'dnf-command(copr)' sudo dnf copr enable -y @caddy/caddy sudo dnf install -y caddy ;; *) bail "https://caddyserver.com/docs/install" ;; esac log "Caddy $(caddy version | cut -d' ' -f1) installed" else log "Caddy already installed" fi # ── Clone / Pull ───────────────────────────────── if [ -d "$INSTALL_DIR" ]; then info "Updating repository..." cd "$INSTALL_DIR" git fetch origin "$BRANCH" git reset --hard "origin/$BRANCH" log "Updated to latest commit" else info "Cloning repository..." git clone --depth=1 -b "$BRANCH" "$REPO_URL" "$INSTALL_DIR" cd "$INSTALL_DIR" log "Repository cloned" fi INSTALL_DIR="$(cd "$INSTALL_DIR" && pwd)" # ── .env ───────────────────────────────────────── if [ -f "$INSTALL_DIR/.env" ]; then warn ".env already exists — not overwriting" else cp "$INSTALL_DIR/.env.example" "$INSTALL_DIR/.env" log "Created .env from .env.example" fi # ── Data directory ──────────────────────────────── mkdir -p "$INSTALL_DIR/data" log "Data directory ready" STANDALONE_DIR="$INSTALL_DIR/.next/standalone" # ── Build dependencies (build-essential for native modules) ── if ! command -v g++ &>/dev/null; then info "Installing build tools (needed for native modules)..." case "$OS" in ubuntu|debian) sudo apt install -y -qq build-essential python3 ;; arch|endeavouros) sudo pacman -S --noconfirm base-devel python ;; fedora|centos|rhel|*) sudo dnf groupinstall -y "Development Tools" && sudo dnf install -y python3 ;; esac log "Build tools installed" fi # ── Build (skip if already built) ────────────────── if [ -f "$STANDALONE_DIR/server.js" ]; then info "Build already exists — skipping" else info "Installing npm dependencies (may take 1-5 min)..." cd "$INSTALL_DIR" rm -rf node_modules 2>/dev/null || true if ! timeout 420 npm ci 2>&1; then err "npm ci failed. Check npm registry access or try manually:" err " cd $INSTALL_DIR && npm ci" exit 1 fi log "Dependencies installed" info "Building Next.js app (this may take a few minutes)..." info "Tip: run in 'screen -S worst-scan' to survive SSH drops" npm run build log "Build complete" # Ensure static files are copied if [ ! -d "$STANDALONE_DIR/.next/static" ]; then mkdir -p "$STANDALONE_DIR/.next" cp -r "$INSTALL_DIR/.next/static" "$STANDALONE_DIR/.next/static" fi cp -r "$INSTALL_DIR/public" "$STANDALONE_DIR/public" 2>/dev/null || true fi # ── systemd service ────────────────────────────── SERVICE_NAME="worst-scan-web" APP_USER="$USER" APP_HOME="$HOME" if [ -f "/etc/systemd/system/$SERVICE_NAME.service" ]; then info "systemd service already exists — restarting" sudo systemctl restart "$SERVICE_NAME" sudo systemctl enable "$SERVICE_NAME" 2>/dev/null || true log "systemd service $SERVICE_NAME restarted" else info "Creating systemd service..." sudo tee "/etc/systemd/system/$SERVICE_NAME.service" > /dev/null </dev/null; then warn "Caddy config already contains worst-scan-web block — not modifying" else sudo mkdir -p "$(dirname "$CADDYFILE")" if [ -s "$CADDYFILE" ] && [ "$(wc -l < "$CADDYFILE")" -gt 2 ]; then EXISTING_IMPORT=$(grep -c 'import' "$CADDYFILE" 2>/dev/null || true) if [ "$EXISTING_IMPORT" -eq 0 ]; then sudo tee -a "$CADDYFILE" > /dev/null < /dev/null </dev/null || true sudo systemctl restart caddy log "Caddy configured" # ── Wait for health ────────────────────────────── info "Waiting for web service..." for i in $(seq 1 15); do if curl -fsS http://127.0.0.1:3000/api/health >/dev/null 2>&1; then log "Web service is healthy"; break fi if [ "$i" -eq 15 ]; then warn "Timed out — check: journalctl -u $SERVICE_NAME -f"; fi sleep 2 done # ── IP ────────────────────────────────────────── IP=$(curl -fsSL http://checkip.amazonaws.com 2>/dev/null || curl -fsSL https://api.ipify.org 2>/dev/null || echo "localhost") # ── Summary ────────────────────────────────────── BOX_W=58 hr() { printf "║ %-*s ║\n" "$((BOX_W-6))" "$1"; } printf "\n" printf "╔══════════════════════════════════════════════════════════════╗\n" printf "║ ${GREEN}worst-scan-web installed!${NC} ║\n" printf "╠══════════════════════════════════════════════════════════════╣\n" hr "Web: http://$IP" hr "Setup: http://$IP/setup" hr "Config: $INSTALL_DIR/.env" hr "Repo: $INSTALL_DIR" hr "" hr "Manage: sudo systemctl restart $SERVICE_NAME" hr "Logs: journalctl -u $SERVICE_NAME -f" hr "Update: cd $INSTALL_DIR && git pull && npm run build" hr " && sudo systemctl restart $SERVICE_NAME" hr "" hr "Caddy: sudo systemctl restart caddy" hr "Cfg: $CADDYFILE" printf "╚══════════════════════════════════════════════════════════════╝\n" printf "\n"